Elasticsearchのbool queryを利用してAND OR NOTを書いてみる

初めてElasticsearchのクエリをビルドしたのでいろいろハマりました。SQLの世界観とちょっと違っていて、なれるまで時間がかかると思います。でも、なれたら複雑な検索条件をSQLより簡単に書けるかも知れません。

では、Elasticsearchの基礎クエリの一つ、`Bool Query`を解説してみます。

# Bool Queryについて

Elasticsearch 2.0から[andクエリ](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/query-dsl-and-query.html)と[orクエリ](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/query-dsl-or-query.html)は全部非推奨になり、その代わりに[boolクエリ](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/query-dsl-bool-query.html)の方が推奨されます。Boolクエリは複数のクエリを組み合わせる（つまりAND、OR、NOTで結合）のに使います。

Boolクエリは４種類があります：must、 filter、 should、 must_notです。

| クエリ | 説明 |
|-------|------|
|must   |ANDとして使います。小クエリは総合スコアに影響を与えます|
|filter| mustと同じくANDです。ただし、スコアは無視されます（総合スコアに影響を与えません）。|
|should|ORとして使えます。 `minimum_should_match`パラメータで最低マッチする条項の数が指定できます[^minimum_should_match]。|
|must_not|NOTです。|

# 使用例

## AND条件

例えば、SQLはこういうSELECT文があります。

```postgres
SELECT * FROM users WHERE age >= 25 AND salary >= 500000;
```

Elasticsearchのクエリに書き直すと、

```json
{
  "query": {
    "bool" : {
      "must" : [
        { "range" : { "age" : { "gte": 25 } } },
        { "range" : { "salary" : { "gte": 500000  } } }
      ]
    }
  }
}
```

になります。今回は`must`を使いますが、スコア無視したい場合は`filter`を使えばいいのです。


## OR条件

```postgres
SELECT * FROM users WHERE age >= 25 OR salary >= 500000;
```

Elasticsearchのクエリに書き直すと、

```json
{
  "query": {
    "bool" : {
      "should" : [
        { "range" : { "age" : { "gte": 25 } } },
        { "range" : { "salary" : { "gte": 500000  } } }
      ],
      "minimum_should_match" : 1
    }
  }
}
```

になります。

## NOT条件

```postgres
SELECT * FROM users WHERE user_id NOT BETWEEN 525 AND 600;
```

Elasticsearchのクエリに書き直すと、

```json
{
  "query": {
    "bool" : {
      "must_not" : {
        "range" : { "user_id" : { "gte": 525, "lte": 600  } }
      }
    }
  }
}
```

になります。

## AND、OR、NOTを組み合わせてみる

```postgres
SELECT * FROM users WHERE (salary >= 500000 AND status = 'active')
                      OR (user_id NOT BETWEEN 525 AND 600);
```

Elasticsearchのクエリに書き直すと、

```json
{
  "query": {
    "bool" : {
      "should" : [
         { 
           "bool" : {
             "must": [
               { "range": { "salary" : { "gte": 500000 } } },
               { "term": {"status": "active" } }
             ]
           } 
         },
         {
           "bool": {
             "must_not" : {
               "range" : { "user_id" : { "gte": 525, "lte": 600 } } 
             }
           }
         } 
       ]
    }
  }
}
```

になります。

注意したいのはboolクエリはネストができるため、いくら複雑な条件にしても表現できます。


# まとめ

| 条件 | SQL | Elasticsearch|
|-----|------|--------------|
|AND| `WHERE A AND B` | `{"bool": {"must": [A,B]}`|
|OR| `WHERE A OR B` | `{"bool": {"should": [A,B]}`|
|NOT| `WHERE NOT A` | `{"bool": {"must_not": A}`|


# おまけ
Elasticsearchクエリは[ポーランド記法](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%BC%E3%83%A9%E3%83%B3%E3%83%89%E8%A8%98%E6%B3%95)と似ていますね。
例えば、`3+4`だったら`+ 3 4`になり、`(1 + 5) * (2 + 3)`だったら`(* (+ 1 5) (+ 2 3))`になりますね。なかなか面白いです！

では、よいサーチライフを！

# 参考文献

* [Bool Query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)
* [Combining Queries](https://www.elastic.co/guide/en/elasticsearch/guide/current/bool-query.html)

[^minimum_should_match]: 公式ドキュメントには書いてないがおそらくデフォルト値は１です。