【Elasticsearch入門】速習Elasticsearch Search query Boolクエリ編

# ゴール
本記事ではElasticsearchの以下の基本操作の中のSearch queryの中の基本クエリ(match_phrase, multi_match)を簡単に紹介します
※最初の方のパートはElasticsearchの説明やデータの準備パートとなっているため [【Elasticsearch入門】速習Elasticsearch Search query 基本クエリ(match, match_all)編](https://qiita.com/doiken_/items/062d5391146b5b0b6618)と内容が被っているので読み飛ばしても構いません。

- CRUD
- Search
    - query
        - 基本クエリ
        - Termクエリ
        - Boolクエリ
            - must
            - should
            - must_not
            - filter
    - aggregation

# Elasticsearch
Elasticsearch は オープンソースの全文検索エンジンです。
Apache LuceneをベースとしたJavaで書かれたソフトウェアでElastic社により開発が進められています。
登録したドキュメントから目的の単語を含むドキュメントを高速に検索することができます。
基本的にElasticsearchではRestful APIを使って操作します。

# バージョン等
Elasticsearch: 7.6.2
kibana: 7.6.2
※ 簡単にSearch queryの操作を行うためにKibanaのDevToolsを使用します。

# 環境準備
環境に関しては以下の投稿に記載の通りで用意しています。
[【Elasticsearch入門】環境構築](https://qiita.com/doiken_/items/fbbaf835710a1827e2f1)
[【Elasticsearch入門】環境構築 Windows編](https://qiita.com/doiken_/items/7847f8fd2bd65ceeaf18)

# 準備
サンプルデータの用意をしていきます。
## ecommerceデータ
Kibanaの初期画面で「Add sample data」を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119747/617075df-05da-01c6-d516-188077c76912.png)
以下の画面に遷移するので一番左の「Sample eCommerce orders」の「Add data」を押下してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119747/187c0277-111a-9fd2-97be-0fc48246da4f.png)
そうするとサンプルデータが用意されます。
Dev Toolsに移動して以下のクエリを投げてください。

```
GET kibana_sample_data_ecommerce/_search
```
レスポンスによりデータがあることが確認できると思います。

## 自作データ
KibanaでDev Toolsに移動して以下のクエリを実行して５つのドキュメントがあるインデックスを作成してください。
（ちなみにこれらの`content`の内容はElasticsearchの[公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-intro.html)に記載されているものです。時間があれば読んでみてください。勉強になります。）

```
PUT my_index/_doc/1
{
  "title": "Elasticsearch",
  "content": "Elasticsearch is the distributed search and analytics engine at the heart of the Elastic Stack. Logstash and Beats facilitate collecting, aggregating, and enriching your data and storing it in Elasticsearch. Kibana enables you to interactively explore, visualize, and share insights into your data and manage and monitor the stack. Elasticsearch is where the indexing, search, and analysis magic happens."
}

PUT my_index/_doc/2
{
  "title": "Kibana",
  "content": "Elasticsearch is the distributed search and analytics engine at the heart of the Elastic Stack. Logstash and Beats facilitate collecting, aggregating, and enriching your data and storing it in Elasticsearch. Kibana enables you to interactively explore, visualize, and share insights into your data and manage and monitor the stack. Elasticsearch is where the indexing, search, and analysis magic happens."
}

PUT my_index/_doc/3
{
  "title": "Elasticsearch",
  "content": "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
}

PUT my_index/_doc/4
{
  "title": "Kibana",
  "content": "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
}

PUT my_index/_doc/5
{
  "title": "Elastic Stack",
  "content": "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
}

```

以下のクエリでインデックスが作成できたことを確認してください。

```
GET my_index/_search
```
確認ができたら`Bool`クエリを使ってみましょう。


# クエリの紹介
## Boolクエリ
Boolクエリは基本クエリを複数組み合わせて複合クエリのように扱うクエリとなります。

### 基本構文

```
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [ <基本クエリ>, <基本クエリ>,,, ],
      "should": [ <基本クエリ>, <基本クエリ>,,, ],
      "must_not": [ <基本クエリ>, <基本クエリ>,,, ],
      "filter": [ <基本クエリ>, <基本クエリ>,,, ]
    }
  }
}
```
上記のようにbool句の中に, `must`, `should`, `must_not`, `filter`の４種類の句を自由に組み合わせることができます。

### must句
must句では必ず含まれているべき条件を指定します。
複数の基本クエリを指定した場合は条件が全て満たされる必要があります。

**例**
リクエスト

```
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "Elasticsearch"}},
        {"match": {"content": "problem"}}
      ]
    }
  }
}
```

レスポンス

```
{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.7706487,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.7706487,
        "_source" : {
          "title" : "Elasticsearch",
          "content" : "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
        }
      }
    ]
  }
}
```

レスポンスをみると`title`で`Elasticsearch`, `content`で`problem`がマッチするものが引っかかっていることがわかると思います。

### should句
shouldの中に複数の基本クエリを指定した場合、いずれかの条件を満たせばドキュメントが引っかかります。
つまり、OR検索と同じ意味になります。

**例**
リクエスト

```
GET my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "elasticsearch"}},
        {"match": {"content": "problem"}}
      ]
    }
  }
}
```

レスポンス

```
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.7706487,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.7706487,
        "_source" : {
          "title" : "Elasticsearch",
          "content" : "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.9395274,
        "_source" : {
          "title" : "Elasticsearch",
          "content" : "Elasticsearch is the distributed search and analytics engine at the heart of the Elastic Stack. Logstash and Beats facilitate collecting, aggregating, and enriching your data and storing it in Elasticsearch. Kibana enables you to interactively explore, visualize, and share insights into your data and manage and monitor the stack. Elasticsearch is where the indexing, search, and analysis magic happens."
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.8311213,
        "_source" : {
          "title" : "Kibana",
          "content" : "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 0.8311213,
        "_source" : {
          "title" : "Elastic Stack",
          "content" : "While not every problem is a search problem, Elasticsearch offers speed and flexibility to handle data in a wide variety of use cases:"
        }
      }
    ]
  }
}
```

レスポンスをみると`title`で`Elasticsearch`, `content`で`problem`のいずれかがマッチするものが引っかかっていることがわかると思います。

また、[【Elasticsearch入門】速習Elasticsearch Search query 基本クエリ(match, match_all)編](https://qiita.com/doiken_/items/062d5391146b5b0b6618)でも紹介した`minimum_should_match`をこちらでも利用できます。
こちらはshould句の中に「指定した複数条件のうち、最低N個以上の条件をみたす。」という使い方をできます。

以下のような使い方となります。

```
GET my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "elasticsearch"}},
        {"match": {"content": "problem"}},
        {"match": {"content": "data"}}
      ],
      "minimum_should_match": 2
    }
  }
}
```

### must_not句
must_not句の中に指定した基本クエリに当てはまるものは検索結果から除外されます。
つまり、should句の否定となります。

**例**
リクエスト

```
GET my_index/_search
{
  "query": {
    "bool": {
      "must_not": [
        {"match": {"title": "elasticsearch"}},
        {"match": {"content": "problem"}}
      ]
    }
  }
}
```

レスポンス

```
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.0,
        "_source" : {
          "title" : "Kibana",
          "content" : "Elasticsearch is the distributed search and analytics engine at the heart of the Elastic Stack. Logstash and Beats facilitate collecting, aggregating, and enriching your data and storing it in Elasticsearch. Kibana enables you to interactively explore, visualize, and share insights into your data and manage and monitor the stack. Elasticsearch is where the indexing, search, and analysis magic happens."
        }
      }
    ]
  }
}

```

レスポンスをみると`title`で`Elasticsearch`, `content`で`problem`のどちらともマッチしないものが引っかかっていることがわかると思います。
また、should句の結果と合わせると一つのインデックスになっているのもわかるかと思います。

### filter句
最後はfilter句です。
filter句の特徴はこれまで紹介してきた句の特徴と少し異なります。
今までの句では検索条件の関連度に基づきスコアが返却されます。
一方でfilter句は検索条件にマッチするかしないかのみが返されます。
（これらの違いによる「Queryコンテキスト」、「Filterコンテキスト」という分類がある）


**例**
リクエスト

```
GET kibana_sample_data_ecommerce/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"products.product_name": "shirt"}}
      ],
      "filter": [
        {"range": 
          {
            "taxful_total_price": {
              "gte": 10,
              "lte": 20
            } 
          }
        }
      ]
    }
  }
}
```

レスポンス

```
...
 {
        "_index" : "kibana_sample_data_ecommerce",
        "_type" : "_doc",
        "_id" : "3p4NgHMBFnhBnJdY3gCV",
        "_score" : 1.8671052,
        "_source" : {
          "category" : [
            "Men's Clothing"
          ],
          "currency" : "EUR",
          "customer_first_name" : "Sultan Al",
          "customer_full_name" : "Sultan Al Richards",
          "customer_gender" : "MALE",
          "customer_id" : 19,
          "customer_last_name" : "Richards",
          "customer_phone" : "",
          "day_of_week" : "Saturday",
          "day_of_week_i" : 5,
          "email" : "sultan al@richards-family.zzz",
          "manufacturer" : [
            "Elitelligence",
            "Low Tide Media"
          ],
          "order_date" : "2020-08-01T02:42:43+00:00",
          "order_id" : 581482,
          "products" : [
            {
              "base_price" : 7.99,
              "discount_percentage" : 0,
              "quantity" : 1,
              "manufacturer" : "Elitelligence",
              "tax_amount" : 0,
              "product_id" : 11389,
              "category" : "Men's Clothing",
              "sku" : "ZO0562105621",
              "taxless_price" : 7.99,
              "unit_discount_amount" : 0,
              "min_price" : 3.6,
              "_id" : "sold_product_581482_11389",
              "discount_amount" : 0,
              "created_on" : "2016-12-24T02:42:43+00:00",
              "product_name" : "Basic T-shirt - green",
              "price" : 7.99,
              "taxful_price" : 7.99,
              "base_unit_price" : 7.99
            },
            {
              "base_price" : 11.99,
              "discount_percentage" : 0,
              "quantity" : 1,
              "manufacturer" : "Low Tide Media",
              "tax_amount" : 0,
              "product_id" : 17390,
              "category" : "Men's Clothing",
              "sku" : "ZO0438604386",
              "taxless_price" : 11.99,
              "unit_discount_amount" : 0,
              "min_price" : 5.4,
              "_id" : "sold_product_581482_17390",
              "discount_amount" : 0,
              "created_on" : "2016-12-24T02:42:43+00:00",
              "product_name" : "Print T-shirt - multicoloured",
              "price" : 11.99,
              "taxful_price" : 11.99,
              "base_unit_price" : 11.99
            }
          ],
          "sku" : [
            "ZO0562105621",
            "ZO0438604386"
          ],
          "taxful_total_price" : 19.98,
          "taxless_total_price" : 19.98,
          "total_quantity" : 2,
          "total_unique_products" : 2,
          "type" : "order",
          "user" : "sultan",
          "geoip" : {
            "country_iso_code" : "AE",
            "location" : {
              "lon" : 54.4,
              "lat" : 24.5
            },
            "region_name" : "Abu Dhabi",
            "continent_name" : "Asia",
            "city_name" : "Abu Dhabi"
          }
        }
      }
...
```

上記は「１０ユーロから２０ユーロの間でトータルの支払いをした客でシャツを買った客」の検索結果を示しています。１０ユーロから２０ユーロの間でフィルターがかけられてその中から`products.product_name`で`shirt`とマッチするものが検索されています。

フィルタのメリットとしてクエリキャッシュというキャッシュ機能があります。
スコアに関連した検索でなく単に検索範囲を限定したい時、フィルタクエリを使うことで性能向上ができます。
フィルタクエリでは検索結果をキャッシュして保持するため、一度検索されると高速に結果を返すことができます。
是非使ってみてください。

以上でboolクエリの解説を終わります。


# おすすめ教材


- [はじめてのElasticsearch](https://www.udemy.com/course/elasticsearch/?couponCode=3BE4592DF570D6DA69CF)
- [はじめてのKibana](https://www.udemy.com/course/kibana-jap/?couponCode=E59890B71A886D5A1CED)