curlコマンドでElasticsearchの基本操作を動かしながら学ぶ！(いろいろな検索クエリ編)

# Elasticsearch のクエリ操作

## クエリの実行

クエリの実行は`_search`エンドポイントに GET メソッドを発行する。

```bash
# test_indexのドキュメントを対象に検索
$ curl -XGET 'http://localhost:9200/test_index/_search'

# test_index1, test_index2の複数インデックスを対象にドキュメントを検索
$ curl -XGET 'http://localhost:9200/test_index1,test_index2/_search'

# test_indexで始まる全てのインデックスを対象にドキュメントを検索
$ curl -XGET 'http://localhost:9200/test_index*/_search'

# クラスタ内の全てのインデックスを対象にドキュメントを検索
$ curl -XGET 'http://localhost:9200/_search'
```

## クエリ DSL

Elasticsearch ではドキュメントを登録する際に JSON オブジェクトとして登録するが、検索を行う際にもクエリを JSON オブジェクトとして記述する。このクエリフォーマットを**クエリ DSL**という

```bash: クエリDSLの概要
# queryパラメータ以外は全てオプションパラメータ
# from/sizeを活用することでページネーションを作成できる
$ curl -XGET 'http://localhost:9200/test_index/_search' \
-H 'Content-Type: application/json' -d'
{
  "query": { <クエリ内容> },
  "from": <検索結果の開始位置>,
  "size": <検索結果の取得件数>,
  "sort": [ <検索結果のソート指定> ],
  "_source": [ <検索結果に含めたいフィールド名(デフォルトは全てのフィールド)> ]
}'
```

:::note info
クエリ DSL に使用する HTTP メソッド
クエリ DSL は基本的には GET メソッドを用いて発行すればよいが、POST メソッドを用いても実行できる。これは、クライアント側のツールによっては GET メソッドをリクエストボディを付加して実行できないため。
:::

## クエリ DSL の種類

クエリ DSL はいくつかの種類があり、大きく次のように分類できる。

- 基本クエリ
  - 全文検索クエリ
  - Term ベースクエリ
- 複合クエリ
  - Bool クエリ

### 基本クエリ

基本クエリはシンプルな単一種類のクエリを指す。

#### 全文検索クエリ

- 全文検索クエリは文章が格納されているフィールドを対象に全文検索をする場合に用いる。
- 単語分割され転置インデックスが構成された text 型のフィールドに対して使用する。

##### match_all クエリ

- 検索条件を指定しなくて必ず全件を取得するクエリ。
- ヒットした関連度を示す`_score`の値も全件に 1.0 の最大値が設定される。
- 主に格納されたドキュメントの確認に使用。

```bash
{
  "query": {
    "match_all": {}
  }
}
```

##### match クエリ

- 基本的な全文検索クエリ。
- match 句の中に、フィールド名および `query` パラメータに検索キーワードを指定する。
- 空白区切りで複数のキーワードを指定することも可能。
- 複数キーワードで検索した場合、デフォルトではいずれか一つの単語が含まれるドキュメントがヒットする(OR 検索)。

```bash
# 以下の場合、titleに"Elasticsearch"か"入門"のどちらかを含むドキュメントがヒットする
{
  "query": {
    "match": {
      "title": {
        "query": "Elasticsearch 入門"
      }
    }
  }
}
```

###### AND 検索

- 複数のキーワードを指定した場合、 and オペレータを指定すると AND 検索を行うことも可能。
- オペレータはデフォルトでは or である。

```bash
# titleに"Elasticsearch"と"入門"の両方を含むドキュメントがヒットする
{
  "query": {
    "match": {
      "title": {
        "query": "Elasticsearch 入門",
        "operator": "and"
      }
    }
  }
}
```

###### より細かい検索条件を指定する

- or 検索と and 検索の中間的な条件として、「最低 N 個以上のキーワードが含まれる」という条件を指定することも可能。
- `minimum_should_match`パラメータに、最低一致数 N を指定する。
- `minimum_should_match`は数値または割合(%)で指定することが可能。

```bash
# "Elasticsearch 入門 query 種類"のうち、最低2個以上のキーワードが含まれるドキュメントがヒットする
# 以下の場合は"minimum_should_match": 50%としても同じ結果が得られる。
{
  "query": {
    "match": {
      "title": {
        "query": "Elasticsearch 入門 query 種類",
        "minimum_should_match": 2
      }
    }
  }
}
```

##### match_phrase クエリ

複数のキーワードを指定した際に、指定された語順でキーワードが含まれるドキュメントのみを検索できるクエリ。

```bash
# 以下の場合、catoon_descriptionに"Tom chased Jerry"という語順でキーワードが含まれるドキュメントがヒットする
# "Jerry chased Tom"という語順ではヒットしない
{
  "query": {
    "match_phrase": {
      "catoon_description": {
        "query": "Tom chased Jerry"
      }
    }
  }
}
```

##### query_string クエリ

検索条件部分に Lucene シンタックスを直接記述できる低レベル検索用のクエリ。

```bash
{
  "query": {
    "query_string": {
      "default_field": "message",
      "query": "message:Elasticsearch^2 +message:world -user_name:Smith"
    }
  }
}
```

#### Term ベースクエリ

- 指定した検索キーワードに完全一致したフィールドを検索するクエリ。
- アナライザによる分割処理がされない keyword 型のフィールドに対して使用する。

##### Term クエリ

- term 句の中にフィールド名と検索キーワードを指定して完全一致検索を行う。
- 完全一致なので、大文字小文字の種別も含め、格納した語句とまったく同じキーワードを指定しないと検索にヒットしない。

```bash
# cityフィールドに"New York"という語句が格納されているドキュメントがヒットする
# これがmatchクエリだと"New Jersey"や"New Orleans"もヒットしてしまう
{
  "query": {
    "term": {
      "city": {
        "value": "New York"
      }
    }
  }
}
```

##### Terms クエリ

- Term クエリと同様に完全一致検索を行うが、検索キーワードを複数指定することができる。
- 指定されたキーワードのうち、いずれか一つでも完全一致するドキュメントがヒットする。

```bash
{
  "query": {
    "terms": {
      "city": ["New York", "New Jersey"]
    }
  }
}
```

##### Range クエリ

主に数値型や日付型のフィールドに対して、値の範囲検索を行うためのクエリ。

```bash
# ageフィールドに20以上30以下の値が格納されているドキュメントがヒットする
{
  "query": {
    "range": {
      "age": {
        "gte": 20,
        "lte": 30
      }
    }
  }
}
```

範囲の比較には以下のオペレータを使用する。

| オペレータ名 | 意味       |
| ------------ | ---------- |
| gte          | 〜以上     |
| lte          | 〜以下     |
| gt           | より大きい |
| lt           | より小さい |

###### 日付型の範囲検索

日付型の範囲検索では、上記のオペレータに加え、特別な日付計算用の式を使用することも可能。

```bash
# "manufactured_date"は製造年月日を格納しているフィールド
# 現在から1週間以内に製造された商品がヒットする
{
  "query": {
    "range": {
      "manufactured_date": {
        "gte": "now-1w"
      }
    }
  }
}
```

日付計算式では、以下のような表現が利用可能。

| 記号       | 意味 |
| ---------- | ---- |
| y          | 年   |
| M          | 月   |
| w          | 週   |
| d          | 日   |
| h または H | 時間 |
| m          | 分   |
| s          | 秒   |

### 複合クエリ - Bool クエリ

- Bool クエリは、基本クエリを組み合わせて複合クエリを構成するための記法。
- 1 つの bool 句の中に、 must、should、must_not、filter の 4 種類のクエリを自由に組み合わせて構成する。 4 種類のクエリの中にさらに複数の基本クエリを指定できる。

```bash
{
  "query": {
    "bool": {
      "must": [ <基本クエリ1>, <基本クエリ2> ],
      "should": [ <基本クエリ3>, <基本クエリ4> ],
      "must_not": [ <基本クエリ5>, <基本クエリ6> ],
      "filter": [ <基本クエリ7>, <基本クエリ8> ]
    }
  }
}
```

#### must クエリ

- must 句は、必ず含まれているべきクエリ条件を記載する。
- must 句の中に複数の基本クエリを指定した場合、その全ての条件が満たされる必要がある。 いわゆる AND 条件の指定と同じ意味。

```bash
# titleに"Elasticsearch"かつcontentに"入門"が含まれるドキュメントがヒットする
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "Elasticsearch" } },
        { "match": { "content": "入門" } }
      ]
    }
  }
}
```

#### should クエリ

- should 句の中に複数の基本クエリを指定した場合、いずれかのクエリ条件を満たせばヒットする。いわゆる OR 条件の指定と同じ意味。
- 複数の条件にヒットした場合、そのドキュメントにはより高いスコアが付与される。
- 'minimum_should_match'パラメータを指定することで、最低限一致するべき条件の数を指定できる。

```bash
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title": "Elasticsearch" } },
        { "match": { "content": "入門" } },
        { "match": { "content": "検索" } },
        { "match": { "content": "全文検索" } }
      ],
      "minimum_should_match": 2 # 50%でも同じ意味
    }
  }
}
```

#### must_not クエリ

must_not 句に指定した基本クエリに当てはまるドキュメントは、検索結果から除外される。いわゆる NOT 条件の指定と同じ意味。

```bash
# titleに"Elasticsearch"が含まれるドキュメントは除外される
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "title": "Elasticsearch" } }
      ]
    }
  }
}
```

#### filter クエリ

- filter 句は主にオプションとして使用される。
- 他の 3 つのクエリは、検索条件の関連度に応じて、スコアを返却するが、filter 句はスコアを返却せず検索条件にマッチするかしないか(Yes or No)のみを返却する。
- いわゆる対象外のドキュメントの「足切り」を行うために使用。
- filter クエリにはクエリキャッシュと呼ばれる機能があり、一度検索条件にヒットした結果はメモリ上にキャッシュされる。再度同じ検索条件で検索を行った場合、キャッシュからヒット結果を取得するため高速に検索できる。

```bash
# 直近1週間に発行されたドキュメントのうち、titleに"Elasticsearch"が含まれるドキュメントがヒットする
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "Elasticsearch" } }
      ],
      "filter": [
        { "range": { "publish_date": { "gte": "now-1w" } } }
      ]
    }
  }
}
```

## クエリ結果のソート

- クエリ結果はデフォルトでスコアによるソートが行われる。
- スコア以外のソートを行いたい場合、`sort`パラメータにソート条件を指定する。

```bash
{
  "query": { <クエリの検索条件> },
  "sort": [
    { "stock_price": { "order": "desc" } }
  ]
}
```

### フィールド名を複数指定したソート

複数のフィールドを指定したソートを行う場合、先に記述したソート条件で同じ順位のドキュメントがあった場合、それらを対象に次のソート条件を適用する。

```bash
# まずstock_priceで降順ソートし、同じ順位のドキュメントがあった場合、founded_dateで昇順ソートする
{
  "query": { <クエリの検索条件> },
  "sort": [
    { "stock_price": { "order": "desc" } },
    { "founded_date": { "order": "asc" } }
  ]
}
```

### スコアに基づいたソートを明示的に指定するケース

"_score"と記載すると関連度スコアに基づいてソートできる。デフォルトでもスコアによるソートが行われるが、例えば上記の複数条件の指定する場合に、フィールド値のソートと組み合わせて、明示的に関連度スコアのソートを指定できる。

```bash
# まずstock_priceで降順ソートし、同じ順位のドキュメントがあった場合、関連度スコアで降順ソートする
{
  "query": { <クエリの検索条件> },
  "sort": [
    { "stock_price": { "order": "desc" } },
    "_score"
  ]
}
```

### 配列型データの算術計算結果に基づいたソート

フィールドの値が配列型を取る場合、配列の値の最小、最大、平均といった算術結果に基づいたソート条件を記載できる。

```bash
# dayoffは月毎の休暇日数を格納している配列型フィールド
# 例: [3, 0, 1, 0, 0, 0, 0, 3, 0, 2, 2, 4]
{
  "query": { <クエリの検索条件> },
  "sort": [
    { "dayoff": { "order": "desc", "mode": "avg" } }
  ]
}
```

算術計算で利用可能な表現は以下の通り。

| 表現   | 意味   |
| ------ | ------ |
| avg    | 平均   |
| min    | 最小   |
| max    | 最大   |
| sum    | 合計   |
| median | 中央値 |