Elasticsearch で検索してみよう

BLOG

こんにちは。
サイオステクノロジーの田川です。

今回は、インデックスに登録したドキュメントを検索してみたいと思います。

対象者

  • Elastic Cloud のアカウントを持っている人(トライアルライセンスを含む)
  • Elasticsearch の初心者

できるようになること

  • Elastic Cloud の Console から、ドキュメントに対して簡単な検索を行える。

前提条件

  • Elastic Cloud (version: 8.15.0)
  • Elastic Cloud 上のインデックスにドキュメントを登録済

(2024年10月11日時点の情報を元に記載しています。)

1. 全件を検索

前回登録した「桃太郎」の内容を Elasticsearch の Search API を使って検索してみます。

Search API の詳細な仕様については、下記の公式ドキュメントを参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html

まずは、全ての内容を検索してみます。
初回に作成した “SIOS-BLOG-SAMPLE-1” のデプロイメント内の Console を表示します。

(Console 画面の表示方法については、 Elastissearch へのインデックスの作成 のブログ記事を参照してください。)
Console のリクエスト欄に下記を入力して、 ▷ をクリックします。

GET /momotaro/_search

全149ドキュメントがヒットしますが、右側のレスポンス欄には10件のみが表示されます。
これは、レスポンスとして返却される件数(“size”パラメータ)のディフォルトが 10 となっているためです。

{
  ...
  "hits": {
    "total": {
      "value": 149,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      ...
      (全10件分のドキュメント)
      ...
    ]
}

※注
ディフォルトでは、score の大きい順にソートされますが、
この例では、検索条件やソート条件を全て省略しているので、並び順にさほど意味はありません。

149件全部を表示したい場合は、”size” に 149以上の値を設定する必要があります。
(なお、”size” の最大値は、10000となっています。)

“size” の詳細については、下記を参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html

下記のリクエストを Console から発行してみます。

GET /momotaro/_search
{
  "size": 150
}

すると、右側のレスポンス欄には、全149件が表示されます。

※注
通常は、必要以上にレスポンスを取得しないことをお勧めいたします。
上記で、”size” に 150 を指定しているのは、あくまでも、”size” の働きを確認するためです。

2. 検索条件付きの検索

2.1 キーワードを1つだけ指定した検索

今度は、本文に「桃太郎」を含むドキュメントのみ検索してみます。

“match” を使って検索してみます。
“match” クエリの詳細は、下記を参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html

下記のリクエストを Console から発行してみます。

GET /momotaro/_search
{
  "query": {
    "match": {
      "content": "桃太郎"
    }
  }
}

すると、40件のドキュメントがヒットしました。

レスポンスの例:

{
  "took": 1,
  ...
  "hits": {
    "total": {
      "value": 40,
      "relation": "eq"
    },
    "max_score": 6.645262,
    "hits": [
      ...
      10件分のヒット内容
      ...
    ]
  }
}

score が大きい順(検索条件に、よりマッチした順)での上位10件が表示されます。

“size” に 40 を指定して、”桃太郎”にマッチした全件を見てみます。

GET /momotaro/_search
{
  "query": {
    "match": {
      "content": "桃太郎"
    }
  },
  "size": 40
}

すると、下位の方では、”桃太郎”ではなく、”桃”にヒットしていることがわかります。

レスポンスの例:

{
  ...
  "content": "...さっきの桃を重そうにかかえて来て、"
  ...
}

これは、検索用に指定した文字列の “桃太郎” が “桃”, “太”, “郎” に分解されてしまい、
“桃”, “太”, “郎” それぞれによる検索が行われているためです。

今回のサンプルでは、形態素解析を行う analyzer を指定していません。
ディフォルトの analyzer として standard analyzer が適用されますが、
standard analyzer が、そのような仕様(“桃太郎”が”桃”,”太”,”郎”に分解される)になっているためです。

standard analyzer を利用した場合に、”桃太郎”がどのように解析されるのか?を
Analyze APIを使って確認してみます。

リクエスト:

GET _analyze
{
  "analyzer": "standard",
  "text": "桃太郎"
}

レスポンス:

{
  "tokens": [
    {
      "token": "桃",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "太",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "郎",
      "start_offset": 2,
      "end_offset": 3,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    }
  ]
}

このように “桃太郎” が、”桃”, “太”, “郎” に分解されているのがわかります。

Analyze API の詳細な説明については、下記を参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html

正直なところ、何の対策もせずに standard analyzer を使って日本語を検索するのは、あまり好ましくありません。

念のため検索文字列に、”桃 太 郎” のように “桃” と “太” と “郎” の間に半角空白を入れて検索した場合にも
40件がヒットすることがわかります。

(*脚注1)1

どうしても、”桃太郎”に合致するドキュメントのみを取得したい場合には、
下記のように、”match” ではなく “match_phrase” を指定してみてください。

“match_phrase” だと、”桃” と “太” と “郎” の並びが”桃太郎”になっているドキュメントのみがマッチします。

GET /momotaro/_search
{
  "query": {
    "match_phrase": {
      "content": "桃太郎"
    }
  },
  "size": 40
}

すると、ヒットする件数が31件となり、”桃太郎”を含むドキュメントのみが取得されます。

2.2 キーワードを2つ指定した検索(OR検索)

次に、「桃太郎」または「きびだんご」を含むドキュメントを検索してみます。

Elasticsearch の場合、”a” または “b” を含むドキュメントを検索したい場合は、
検索対象文字列に “a b” のように半角空白で連結して記載します。

GET /momotaro/_search
{
  "query": {
    "match": {
      "content": "桃太郎 きびだんご"
    }
  }
}

108件に増えました。

これは、今回の環境では “match” による検索を行うと、
“桃”,”太”,”郎”,”き”,”び”,”だ”,”ん”,”ご”
に分解されて、どれか 1文字でもマッチしたドキュメントが検索されるためです。

この検索結果の改善は、次回、行っていきたいと思います。

2.3 キーワードを2つ指定した検索(AND検索)

では、「桃太郎」と「きびだんご」の両方を含むドキュメントを検索してみます。

Elasticsearch では、”a” を含み、かつ、”b” を含むドキュメントを検索したい場合、
いくつかの書き方があります。

(1) operator に “and” を指定する。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-boolean

(2) bool を指定する。
(2.1) bool と filter を指定する。
(2.2) bool と must を指定する。
(2.3) bool と should を指定する。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

(2.2) の bool と must を指定する方法の例です。

GET /momotaro/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "content": "桃太郎"
          }
        },
        {
          "match_phrase": {
            "content": "きびだんご"
          }
        }
      ]
    }
  }
}

5件になりました。

2.4 キーワードを2つ指定した検索(片方を含み、もう片方を含まない検索)

では、「桃太郎」を含むが、「きびだんご」を含まないドキュメントを検索してみます。

“must_not”を使います。

GET /momotaro/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "content": "桃太郎"
          }
        }
      ],
      "must_not": [
        {
          "match_phrase": {
            "content": "きびだんご"
          }
        }
      ]
    }
  }
}

26件がヒットしました。

“桃太郎”を含むドキュメントが31件、
“桃太郎” と “きびだんご” の両方を含むドキュメントが5件なので、
31 – 5 = 26 と、合っています。

(*脚注2)2

3. ソート

ディフォルトでは、score が大きい順(どれだけ検索条件にマッチしているか?の順)に並び替えされますが、
業務上の要件で、指定した順序で並び替えを行いたい場合もあるかと思います
(例: ドキュメントの最終更新日時が新しい順にソートしたい、など)。

そのような場合には、”sort” を指定します。

ここでは、試しに、chunk_no の昇順にソートしてみます。

GET /momotaro/_search
{
  "query": {
    "match_phrase": {
      "content": "桃太郎"
    }
  },
  "sort": [
    "chunk_no"
  ]
}

これで、chunk_no の昇順に結果を得ることができます。

次の10件(11件目-20件目)を取得するには、”from” に 10 を指定します。

GET /momotaro/_search
{
  "query": {
    "match_phrase": {
      "content": "桃太郎"
    }
  },
  "sort": [
    "chunk_no"
  ],
  "from": 10
}

上記のリクエストを発行すると、11件目から20件目が表示されます。

※レスポンスだけを見ると、この結果が11件目から20件目であることはわかりません。
リクエスト時点で何件目から何件目までを取得しようとしているのかをきちんと把握しておく必要があります。

※”from”を指定することで、ヒットするドキュメントが大量にある場合に、少しずつ結果を取得することが可能となります。

“sort”, “from” の詳細については、それぞれ、下記を参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html

(*脚注3)3

4. 取得するフィールドの指定

ディフォルトだと、全てのフィールドを取得します。
仮に、取得する必要がないフィールドがあった場合は、下記のように、
“_source”: false を指定し、かつ、”fields” に取得したいフィールド名を配列で指定します。
(下記の例では、”fields” に1つのフィールドのみ指定していますが、複数フィールドを指定可能です。)

取得するフィールドを限定することで、クエリ実行時に消費するリソースを抑えることができます。

GET /momotaro/_search
{
  "_source": false,
  "fields": [
    "content"
  ],
  "query": {
    "match_phrase" : {
      "content" : "桃太郎"
    }
  }
}

レスポンスの例:

{
  ...
  "hits": {
    ...
    "hits": [
      {
        "_index": "momotaro",
        "_id": "********************",
        "_score": 6.645262,
        "fields": {
          "content": [
            "「桃太郎さん、桃太郎さん、どちらへおいでになります。」"
          ]
        }
      },
      ...
    ]
  }
}

“fileds” の詳細は、下記を参照してください。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html

今回、紹介したクエリ以外にもいろんな種類のクエリを利用可能です。
興味がある方は、ぜひ、お試しください。

今回は、検索はできるようになりましたが、日本語の検索でうまくいかない部分がありました。
次回は、日本語用の形態素解析を行った上で、検索を行ってみたいと思います。


(*脚注)

  1. anlayzerに standard ではなく、日本語用の analyzer を明示することで、
    “match”:{“content”:”桃太郎”} でも”桃太郎”にマッチするドキュメントのみを取得することは可能です。
    ただし、記事の内容が多くなってしまうので、今回は触れずに、次回のブログ記事で書きたいと思います。 ↩︎
  2. must_not とはやや目的が異なりますが、score を減点する方法もあります。
    – 「桃太郎」を含む場合 score を加点する。
    – 「きびだんご」を含む場合 score を減点する。
    scrore を減点したい場合、”negative” や “negative_boost” を指定します。
    そういった細かい調整が、Elasticsearchでは可能です。
    https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html ↩︎
  3. “from” を指定する以外に、”search_after”を指定する方法もあります。
    https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#search-after ↩︎