Easysearchにおけるデータ操作と高度な検索クエリ

Easysearchの概要

Easysearchは、Apache Luceneを基盤とするオープンソースの分散型検索・分析エンジンであるElasticsearchと高い互換性を持つ国産代替ソリューションです。Elasticsearch 7.10.2のオープンソース版をベースに開発されており、機能、パフォーマンス、安定性、拡張性において強化が図られています。既存のElasticsearchアプリケーションからの移行は、ビジネスコードの変更をほとんど必要とせず、Query DSLや基本的なSQL構文、既存のSDKに対応しているため、スムーズな移行が可能です。

Easysearchクラスターの基本概念

Easysearchクラスターは、以下の主要な概念で構成されます。

  1. ノード (Node): クラスター内の個々のサーバーで、データの保存とインデックス/検索機能に参加します。
  2. クラスター (Cluster): 一意のクラスター名を持つ1つ以上のノードの集合で、データインデックスとクエリタスクを共同で実行します。
  3. インデックス (Index): 関連データを格納する論理的なコンテナで、RDBにおけるデータベースに相当します。1つのインデックスは複数のドキュメントを含みます。
  4. ドキュメント (Document): インデックス内の基本的なデータ単位で、RDBにおける行に相当します。
  5. フィールド (Field): ドキュメント内の属性で、RDBにおける列に相当します。
  6. シャード (Shard): パフォーマンスとスケーラビリティ向上のため、インデックスは複数のシャードに分割されます。各シャードはインデックスの一部です。
  7. レプリカ (Replica): シャードの複製で、データの信頼性とノード障害時の可用性を高めます。

_cluster/health_cluster/statsなどのAPIを利用して、クラスターの状態や詳細情報を簡単に確認でき、これらはEasysearchクラスターの運用と最適化に不可欠です。

クラスター情報の確認

Easysearchでは、複数のAPIを用いてクラスターの健全性、ノード情報、インデックスの状態など、様々な情報を取得できます。以下に主要なAPIと使用例を示します。

クラスター健全性の確認

_cluster/health APIは、クラスターの全体的な健全性(正常か、ノード数、シャードの状態など)を返します。

GET /_cluster/health

実行結果例:

{
  "cluster_name": "production_cluster",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 4,
  "number_of_data_nodes": 3,
  "active_primary_shards": 8,
  "active_shards": 16,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 0,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 100.0
}

クラスター状態の詳細確認

_cluster/stats APIは、インデックス、ノード、シャードなど、クラスターのより詳細な統計情報を表示します。

GET /_cluster/stats

実行結果例:

{
  "cluster_name": "production_cluster",
  "status": "green",
  "indices": {
    "count": 12,
    "shards": {
      "total": 24,
      "primaries": 12,
      "replication": 1.0,
      "index": {
        "shards": {
          "min": 1,
          "max": 6,
          "avg": 2.0
        }
      }
    }
  },
  "nodes": {
    "count": {
      "total": 4,
      "data": 3,
      "coordinating_only": 0,
      "master": 1,
      "ingest": 2
    },
    "os": {
      "available_processors": 16,
      "allocated_processors": 16
    },
    "process": {
      "cpu": {
        "percent": 8
      },
      "open_file_descriptors": {
        "min": 120,
        "max": 350,
        "avg": 220
      }
    }
  }
}

ノード情報の確認

_nodes APIは、クラスター内の各ノードの詳細情報(役割、IPアドレス、メモリ使用量など)を提供します。

GET /_nodes

実行結果例:

{
  "cluster_name": "production_cluster",
  "nodes": {
    "node_alpha": {
      "name": "data_node_01",
      "transport_address": "10.0.0.10:9300",
      "host": "10.0.0.10",
      "ip": "10.0.0.10",
      "roles": ["master", "data", "ingest"],
      "os": {
        "available_processors": 8,
        "allocated_processors": 8
      },
      "process": {
        "cpu": {
          "percent": 5
        },
        "open_file_descriptors": 250
      }
    },
    "node_beta": {
      "name": "data_node_02",
      "transport_address": "10.0.0.11:9300",
      "host": "10.0.0.11",
      "ip": "10.0.0.11",
      "roles": ["data"],
      "os": {
        "available_processors": 8,
        "allocated_processors": 8
      },
      "process": {
        "cpu": {
          "percent": 12
        },
        "open_file_descriptors": 180
      }
    }
  }
}

インデックス状態の確認

_cat/indices APIは、クラスター内の全インデックスの状態(ドキュメント数、保存サイズ、シャード数など)を簡潔な形式で表示します。

GET /_cat/indices?v

実行結果例:

health status index          uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   user_logs      AxYUc91wRm6QH5P7g0T4Xy   1   1          0            0       300b           300b
green  open   product_data   BxEZih5zToCnA1PpQ8P4Zw   4   1        150            2      12mb           6mb

これらのAPIを使いこなすことで、Easysearchクラスターの監視と管理を効率的に行えます。

データのCRUD操作

Easysearchでのインデックスとドキュメントに対するCRUD (作成、読み取り、更新、削除) 操作は、データ管理の基本です。以下に具体的な例を示します。

インデックスの作成

新しいインデックスを作成し、シャード数とレプリカ数を指定します。

PUT /my_application_index
{
  "settings": {
    "number_of_shards": 4,
    "number_of_replicas": 1
  }
}

インデックスの削除

不要になったインデックスを削除します。

DELETE /my_application_index

ドキュメントの追加または更新

POSTまたはPUTリクエストを使用して、インデックスにドキュメントを追加します。

POST /user_profiles/_doc/user001
{
  "user_name": "Taro Yamada",
  "age": 28,
  "city": "Tokyo"
}
PUT /user_profiles/_doc/user002
{
  "user_name": "Hanako Tanaka",
  "age": 32,
  "city": "Osaka"
}

POST /_doc/{id}は、指定されたIDのドキュメントが存在しない場合は新規作成し、存在する場合は既存ドキュメントを完全に置き換えます。IDを省略すると、Easysearchが自動的にIDを生成します。

PUT /_doc/{id}は、指定されたIDのドキュメントを新規作成または完全に置き換えます。IDの指定は必須です。

どちらの方法も、既存のドキュメントに対して同じIDで実行された場合、ドキュメント全体が上書きされます。

ドキュメントの新規作成(_create)

_createエンドポイントを使用すると、ドキュメントが存在しない場合にのみ新規作成し、既に存在する場合はエラーを返します。

PUT /inventory_items/_create/item_A
{
  "product_code": "ABC-101",
  "stock_quantity": 100
}

既に存在するドキュメントを作成しようとすると、以下のエラーが発生します。

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[item_A]: version conflict, document already exists (current version [1])",
        "index_uuid": "abcXYZ...",
        "shard": "0",
        "index": "inventory_items"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[item_A]: version conflict, document already exists (current version [1])",
    "index_uuid": "abcXYZ...",
    "shard": "0",
    "index": "inventory_items"
  },
  "status": 409
}

ドキュメントの取得

指定されたIDを持つドキュメントの詳細情報を取得します。

GET /user_profiles/_doc/user001

ドキュメントの部分更新

既存のドキュメントの特定のフィールドのみを更新し、他のフィールドは保持します。

POST /user_profiles/_update/user001
{
  "doc": {
    "city": "Nagoya"
  }
}

ドキュメントの削除

指定されたIDを持つドキュメントを削除します。

DELETE /user_profiles/_doc/user002

全ドキュメントの検索

インデックス内のすべてのドキュメントを検索します。

GET /all_products/_search
{
  "query": {
    "match_all": {}
  }
}

一括操作(_bulk API)

_bulk APIは、単一のリクエストで複数のインデックス、削除、更新操作を実行するために使用されます。大量のデータを一括処理する際に非常に有効で、パフォーマンスと効率を大幅に向上させます。

POST /_bulk
{ "index": { "_index": "products", "_id": "P001" } }
{ "product_name": "Laptop", "price": 120000, "category": "Electronics" }
{ "index": { "_index": "products", "_id": "P002" } }
{ "product_name": "Keyboard", "price": 15000, "category": "Electronics" }
{ "update": { "_index": "products", "_id": "P001" } }
{ "doc": { "price": 115000 } }
{ "delete": { "_index": "products", "_id": "P002" } }

_bulk APIのリクエストボディは、複数の操作行とオプションのソースドキュメント行で構成されます。各操作行は、操作タイプ(例: index, create, delete, update)とそのメタデータを示し、ソースドキュメント行は実際のデータを含みます。各操作は改行区切りで、リクエストボディの最後も改行で終わる必要があります。

文字解析器(アナライザー)

Easysearchでは、文字解析器(Analyzer)がテキストを語彙単位(term)に分解し、全文検索やテキスト分析の基礎となります。文字解析器は通常、文字フィルター(Character Filters)、トークナイザー(Tokenizer)、トークンフィルター(Token Filters)から構成されます。

  1. 文字フィルター (Character Filters): テキストをトークナイザーに渡す前に前処理します(例: HTMLタグの除去、文字置換)。
  2. トークナイザー (Tokenizer): テキストを個々のトークンに分解します。これは解析処理の核となる部分です。
  3. トークンフィルター (Token Filters): トークンに対して様々な処理(例: 小文字化、ストップワードの除去、語幹抽出)を適用します。

textタイプのフィールドのみが全文検索をサポートし、その結果は類似度スコアに基づいてランク付けされます。

PUT /news_articles/_mapping
{
  "properties": {
    "article_text": {
      "type": "text",
      "analyzer": "ik_max_word",
      "search_analyzer": "ik_smart"
    }
  }
}
  • article_text: textタイプとして定義され、分詞化と全文検索の対象となります。
  • analyzer: インデックス時に使用される文字解析器としてik_max_wordを指定します。これはテキストを可能な限り多くの語彙に分割し、高い網羅性を目指します。
  • search_analyzer: 検索時に使用される文字解析器としてik_smartを指定します。これはよりスマートな分詞化を行い、検索の精度を高めます。

インデックス時と検索時で異なるアナライザーを使用することも、同じアナライザーを使用することも可能です。ここでは、中国語テキストに特化したIK文字解析器の二つのモードを紹介します。

  • ik_max_word: テキストを可能な限り多く語彙に分割します。高い再現率が求められるシナリオに適しています。
  • ik_smart: 最もインテリジェントな分詞化を行います。高い精度が求められる検索シナリオに適しています。

この設定により、news_articlesインデックスにドキュメントを追加・更新する際、article_textフィールドはik_max_wordで処理され、検索時にはik_smartで処理されることで、効率的かつ高精度な検索が可能になります。

以下に、standardik_smartik_max_word各アナライザーの動作を比較する例を示します。

GET /_analyze
{
  "tokenizer": "standard",
  "text": "検索エンジンとAI"
}

GET /_analyze
{
  "tokenizer": "ik_smart",
  "text": "検索エンジンとAI"
}

GET /_analyze
{
  "tokenizer": "ik_max_word",
  "text": "検索エンジンとAI"
}

実行結果例:

# GET /_analyze (standard)
{
  "tokens": [
    {
      "token": "検索",
      "start_offset": 0,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "エンジン",
      "start_offset": 2,
      "end_offset": 6,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "と",
      "start_offset": 6,
      "end_offset": 7,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    },
    {
      "token": "AI",
      "start_offset": 7,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 3
    }
  ]
}
# GET /_analyze (ik_smart)
{
  "tokens": [
    {
      "token": "検索エンジン",
      "start_offset": 0,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "AI",
      "start_offset": 7,
      "end_offset": 9,
      "type": "CN_WORD",
      "position": 1
    }
  ]
}
# GET /_analyze (ik_max_word)
{
  "tokens": [
    {
      "token": "検索エンジン",
      "start_offset": 0,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "検索",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "エンジン",
      "start_offset": 2,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "AI",
      "start_offset": 7,
      "end_offset": 9,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}

存在しないアナライザーを使用した場合、以下のようなエラーが返されます。

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "failed to find global tokenizer under [non_existent_analyzer]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "failed to find global tokenizer under [non_existent_analyzer]"
  },
  "status": 400
}

厳密一致検索、正規表現検索、ワイルドカード検索

Easysearchにおける厳密一致検索、正規表現検索、ワイルドカード検索は、それぞれ異なるシナリオに対応する検索方法です。

1. 厳密一致検索 (Term Query)

  • 検索語句と完全に一致するドキュメントを検索します。
  • テキスト解析は行われず、キーワード、ID、タグなどのフィールドの厳密な照合に適しています。
  • 構造化データや、数値、日付、ブール値など、テキスト解析が不要なフィールドに利用されます。
{
  "query": {
    "term": {
      "user_status": "active"
    }
  }
}

2. 正規表現検索 (Regexp Query)

  • 正規表現パターンに基づきドキュメントを検索します。
  • 複雑な文字列パターンマッチングをサポートしますが、データ量が多い場合はパフォーマンスが低下する可能性があります。
  • 柔軟で複雑なマッチング条件が必要な場合に適しています。
{
  "query": {
    "regexp": {
      "description": "Easysearch .*flexible"
    }
  }
}

3. ワイルドカード検索 (Wildcard Query)

  • ワイルドカードパターンを使用してドキュメントを検索します。
  • ?(任意の1文字)と*(0文字以上の任意の文字列)をサポートします。
  • ワイルドカード検索は大量のデータをスキャンする可能性があるため、相対的にパフォーマンスが低い点に注意が必要です。
{
  "query": {
    "account_name": "jane*"
    }
  }
}

これらの検索タイプは、それぞれの特性を理解し、適切なシナリオで利用することが重要です。大規模データセットでの頻繁なワイルドカードや正規表現検索は、パフォーマンスへの影響を考慮して慎重に適用すべきです。

以下に、複数のデータを一括でインポートし、これらの異なる検索タイプでクエリを実行する例を示します。

データのバルクインポート

POST /user_accounts/_bulk
{ "index": { "_id": 1 }}
{ "account_name": "suzuki_ichiro", "user_status": "active", "email_address": "ichiro.suzuki@example.jp", "bio": "Ichiro loves distributed search systems and cloud services." }
{ "index": { "_id": 2 }}
{ "account_name": "tanaka_hanako", "user_status": "inactive", "email_address": "hanako.tanaka@example.jp", "bio": "Hanako is a data scientist working with machine learning." }
{ "index": { "_id": 3 }}
{ "account_name": "yamada_taro", "user_status": "active", "email_address": "taro.yamada@example.jp", "bio": "Taro enjoys hiking and mountain climbing." }
{ "index": { "_id": 4 }}
{ "account_name": "sato_alice", "user_status": "active", "email_address": "alice.sato@example.jp", "bio": "Alice is a software engineer specialized in Python." }
{ "index": { "_id": 5 }}
{ "account_name": "goto_bob", "user_status": "inactive", "email_address": "bob.goto@example.jp", "bio": "Bob is an AI enthusiast and deep learning expert." }

1. 厳密一致検索

user_statusが"active"のユーザーを検索します。

GET /user_accounts/_search
{
  "query": {
    "term": {
      "user_status": "active"
    }
  }
}

2. ワイルドカード検索

bioフィールドに"search*"で始まる単語が含まれるドキュメントを検索します。

GET /user_accounts/_search
{
  "query": {
    "wildcard": {
      "bio": "search*"
    }
  }
}

3. 正規表現検索

account_nameが"sato"で始まるユーザーを検索します。

GET /user_accounts/_search
{
  "query": {
    "regexp": {
      "account_name": "sato.*"
    }
  }
}

これらの例を通して、Easysearchでデータを一括インポートし、様々な検索方法を使い分けることで、特定の条件に合致するデータを効率的に検索・分析する方法を理解できます。

複数フィールド検索

Easysearchの複数フィールド検索は、複数のフィールドに対して同時に検索を実行し、より精度の高い結果を得ることを可能にします。最も一般的に使用されるのはmulti_matchクエリです。multi_matchクエリはmatchクエリを拡張したもので、指定されたキーワードを複数のフィールドから検索できます。

multi_matchクエリは、best_fieldsmost_fieldscross_fieldsphrasephrase_prefixなど、複数のマッチングモードをサポートします。各モードの概要は以下の通りです。

  • best_fields: デフォルトモード。最もマッチ度が高いフィールドを選択します。
  • most_fields: 各フィールドのマッチ度を計算し、それらを合計します。
  • cross_fields: 複数のフィールドを1つのフィールドとみなし、テキストが複数のフィールドに分散している場合に特に有効です。
  • phrase: フレーズマッチ。キーワードの順序がクエリと同じであることを保証します。
  • phrase_prefix: フレーズプレフィックスマッチ。キーワードの部分一致も許容します。

まず、knowledge_baseというインデックスにサンプルデータをインポートします。

POST /knowledge_base/_bulk
{ "index": { "_id": 1 } }
{ "doc_title": "分散検索の基本", "doc_content": "分散型検索エンジンのアーキテクチャと利点に関する入門ガイドです。" }
{ "index": { "_id": 2 } }
{ "doc_title": "Easysearch高度利用ガイド", "doc_content": "Easysearchの高度な機能と最適化に関する詳細なガイドです。" }
{ "index": { "_id": 3 } }
{ "doc_title": "実践Easysearch運用", "doc_content": "Easysearchの実践的な運用とトラブルシューティングのガイドです。" }
{ "index": { "_id": 4 } }
{ "doc_title": "Easysearch学習の初歩", "doc_content": "Easysearchを学ぶ初心者のためのガイドです。" }

multi_matchクエリを使用して、doc_titledoc_contentフィールドから同時にキーワードを検索します。

1. 基本的なmulti_matchクエリ

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "ガイド",
      "fields": ["doc_title", "doc_content"]
    }
  }
}

2. マッチングモードをbest_fieldsに指定

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "ガイド",
      "fields": ["doc_title", "doc_content"],
      "type": "best_fields"
    }
  }
}

3. マッチングモードをmost_fieldsに指定

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "ガイド",
      "fields": ["doc_title", "doc_content"],
      "type": "most_fields"
    }
  }
}

4. cross_fieldsモードの使用

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "Easysearch ガイド",
      "fields": ["doc_title", "doc_content"],
      "type": "cross_fields"
    }
  }
}

5. フレーズマッチ (phrase)

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "入門ガイド",
      "fields": ["doc_title", "doc_content"],
      "type": "phrase"
    }
  }
}

6. フレーズプレフィックスマッチ (phrase_prefix)

POST /knowledge_base/_search
{
  "query": {
    "multi_match": {
      "query": "入門ガイ",
      "fields": ["doc_title", "doc_content"],
      "type": "phrase_prefix"
    }
  }
}

multi_matchクエリでは、以下のパラメータが重要です。

  • query: 検索するキーワードまたはフレーズ。
  • fields: 検索対象となるフィールドのリスト。1つ以上指定できます。
  • type: マッチングモードを指定。デフォルトはbest_fieldsです。

multi_matchクエリを利用することで、単一のクエリで複数のフィールドを横断的に検索し、様々な検索要件に対応する強力な機能を提供します。また、boostパラメータを使用することで、特定のフィールドの検索結果における関連性スコアの重みを調整することも可能です。

ブーリアンクエリ

ブーリアンクエリは、Easysearchで非常に強力かつ柔軟な検索を可能にするクエリタイプです。複数の条件を組み合わせて複雑な検索要件を実現します。boolクエリタイプを使用し、以下のサブ句を含めることができます。

  • must: この配列内のクエリ条件はすべて一致する必要があります(論理AND)。
  • filter: この配列内のクエリ条件はすべて一致する必要がありますが、スコアリングには影響しません。パフォーマンスを重視するフィルタリング操作に適しています。
  • must_not: この配列内のクエリ条件はいずれも一致してはなりません(論理NOT)。一致するドキュメントは結果セットから除外されます。
  • should: この配列内のクエリ条件は、少なくとも1つ一致する必要があります。must句がない場合、最低1つのshould句がマッチする必要があります。スコアリングに影響を与えます。
  • minimum_should_match: should句で最低限一致する必要がある条件の数を指定します。

まず、technical_articlesというインデックスを作成し、マッピングを定義します。categoryarticle_titleフィールドは正確なマッチングが必要なため、keywordタイプとして定義します。

PUT /technical_articles
{
  "mappings": {
    "properties": {
      "category": { "type": "keyword" },
      "article_title": { "type": "keyword" }
    }
  }
}

次に、バルクAPIを使用してサンプルデータをtechnical_articlesインデックスにインポートします。

POST /technical_articles/_bulk
{ "index": { "_id": 1 } }
{ "category": "テクノロジー", "article_title": "分散システム設計入門" }
{ "index": { "_id": 2 } }
{ "category": "テクノロジー", "article_title": "クラウドネイティブ開発手法" }
{ "index": { "_id": 3 } }
{ "category": "ビジネス", "article_title": "最新の市場トレンド分析" }
{ "index": { "_id": 4 } }
{ "category": "テクノロジー", "article_title": "マイクロサービスアーキテクチャ" }
{ "index": { "_id": 5 } }
{ "category": "ビジネス", "article_title": "デジタル変革の戦略" }

categoryが「テクノロジー」かつarticle_titleが「分散システム設計入門」のドキュメントを検索するためにmust句を使用します。これはすべての条件が満たされる必要があることを意味します。

POST /technical_articles/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "category": "テクノロジー" } },
        { "term": { "article_title": "分散システム設計入門" } }
      ]
    }
  }
}

同じクエリをfilter句でも実行できます。filter句は関連性スコアに影響を与えないため、スコアリングが不要な高速なフィルタリングに適しています。

POST /technical_articles/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "category": "テクノロジー" } },
        { "term": { "article_title": "分散システム設計入門" } }
      ]
    }
  }
}

どちらのクエリも、以下の結果を返します。

{
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "hits": [
      {
        "_index": "technical_articles",
        "_id": "1",
        "_source": {
          "category": "テクノロジー",
          "article_title": "分散システム設計入門"
        }
      }
    ]
  }
}

さらに複雑なブーリアンクエリの例として、特定の著者による、あるカテゴリに属し、一定の閲覧数範囲外で、特定のタグを持つ記事を検索するシナリオを考えます。

POST /content_items/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "author_id": "elastic_dev" } }
      ],
      "filter": [
        { "term": { "categories": "enterprise" } }
      ],
      "must_not": [
        {
          "range": {
            "views": { "gte": 50, "lte": 150 }
          }
        }
      ],
      "should": [
        { "term": { "tags": "featured" } },
        { "term": { "tags": "popular" } }
      ],
      "minimum_should_match": 1,
      "boost": 1.0
    }
  }
}
  • must句: author_idelastic_devであるドキュメントが必須。
  • filter句: categoriesenterpriseであるドキュメントに絞り込み(スコアには影響しない)。
  • must_not句: viewsフィールドが50から150の範囲内ではないドキュメントを除外。
  • should句: tagsフィールドがfeaturedまたはpopularのいずれかにマッチ(少なくとも1つマッチ)。
  • minimum_should_match: should句の条件のうち、少なくとも1つが一致する必要がある。
  • boost: クエリ全体のスコアを1.0でブースト。

このDSLを試すために、まずcontent_itemsインデックスを作成し、テストデータを挿入します。

インデックス作成

PUT /content_items
{
  "mappings": {
    "properties": {
      "author_id": { "type": "keyword" },
      "categories": { "type": "keyword" },
      "tags": { "type": "keyword" },
      "views": { "type": "integer" }
    }
  }
}

バルクデータインポート

POST /content_items/_bulk
{ "index": { "_id": 1 } }
{ "author_id": "elastic_dev", "categories": ["enterprise", "backend"], "tags": ["featured", "popular"], "views": 200 }
{ "index": { "_id": 2 } }
{ "author_id": "elastic_dev", "categories": ["enterprise"], "tags": ["backend"], "views": 100 }
{ "index": { "_id": 3 } }
{ "author_id": "elastic_dev", "categories": ["frontend"], "tags": ["featured"], "views": 300 }
{ "index": { "_id": 4 } }
{ "author_id": "other_author", "categories": ["enterprise"], "tags": ["popular"], "views": 250 }
{ "index": { "_id": 5 } }
{ "author_id": "elastic_dev", "categories": ["enterprise"], "tags": ["archived"], "views": 70 }

上記のブーリアンクエリを実行すると、以下の条件を満たすドキュメントが返されます。

  1. author_idelastic_devであること。
  2. categoriesenterpriseが含まれること。
  3. viewsが50から150の範囲外であること。
  4. tagsfeaturedまたはpopularのいずれかが含まれること。
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.3769134,
    "hits": [
      {
        "_index": "content_items",
        "_type": "_doc",
        "_id": "1",
        "_score": 1.3769134,
        "_source": {
          "author_id": "elastic_dev",
          "categories": ["enterprise", "backend"],
          "tags": ["featured", "popular"],
          "views": 200
        }
      }
    ]
  }
}

SQL検索

Easysearchは、追加のプラグインなしでSQLクエリを直接サポートし、ElasticsearchのSQL呼び出し方式と互換性があります。これにより、ネイティブSQLクエリを記述して実行することが可能です。

以下は、SQLクエリのテスト例です。

SELECT * FROM product_catalog;
SELECT * FROM product_catalog LIMIT 3;
SELECT * FROM product_catalog ORDER BY item_name;
SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty > 50;
SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty = 20;
SELECT * FROM product_catalog WHERE release_date IS NULL;
SELECT DISTINCT category FROM product_catalog;
SELECT MIN(price), MAX(price), AVG(price) FROM product_catalog;
SELECT category, COUNT(*) AS num_products FROM product_catalog GROUP BY category;

EasysearchでのSQLクエリの実行

EasysearchでSQLクエリを実行するには、_sqlエンドポイントへのPOSTリクエストを使用します。

POST /_sql
{
  "query": "SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty > 50"
}

結果をJSON形式で受け取るには、?format=jsonを追加します。

POST /_sql?format=json
{
  "query": "SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty > 50"
}

以下に、一般的なSQLクエリとそれに対応するPOSTリクエストの例を示します。

1. 全ドキュメントの検索

POST /_sql
{
  "query": "SELECT * FROM product_catalog"
}

2. 返されるドキュメント数の制限

POST /_sql
{
  "query": "SELECT * FROM product_catalog LIMIT 3"
}

3. フィールドによるソート

POST /_sql
{
  "query": "SELECT * FROM product_catalog ORDER BY item_name ASC"
}

4. 条件付き検索

POST /_sql
{
  "query": "SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty > 50"
}

5. 厳密一致検索

POST /_sql
{
  "query": "SELECT item_name, stock_qty FROM product_catalog WHERE stock_qty = 20"
}

6. NULL値の検索

POST /_sql
{
  "query": "SELECT * FROM product_catalog WHERE release_date IS NULL"
}

7. ユニーク値の検索

POST /_sql
{
  "query": "SELECT DISTINCT category FROM product_catalog"
}

8. 集約関数クエリ

POST /_sql
{
  "query": "SELECT MIN(price), MAX(price), AVG(price) FROM product_catalog"
}

9. グループ化と統計

POST /_sql
{
  "query": "SELECT category, COUNT(*) AS num_products FROM product_catalog GROUP BY category"
}

複数インデックス操作のSQLステートメント

以下に、Easysearchにおける複数インデックス(RDBにおける複数テーブル)を操作するSQLステートメントと、その簡単な説明を示します。

1. サブクエリ

SELECT * FROM `employees` emp WHERE emp.dept_id IN (SELECT id FROM `departments`)

このクエリは、departmentsインデックスに存在するidを持つemployeesインデックスのすべてのドキュメントを選択します。

2. 内部結合 (Inner Join)

SELECT * FROM `employees` emp JOIN `departments` dept ON emp.dept_id = dept.id

このクエリは、employeesdepartmentsインデックスの両方でdept_ididが一致するドキュメントを結合して返します。

3. 左外部結合 (Left Join)

SELECT * FROM `employees` emp LEFT JOIN `departments` dept ON emp.dept_id = dept.id

このクエリは、employeesインデックスのすべてのドキュメントを返し、departmentsインデックスに一致するドキュメントがあれば結合します。一致しない場合は、departments側のフィールドはNULLで埋められます。

4. 右外部結合 (Right Join)

SELECT * FROM `employees` emp RIGHT JOIN `departments` dept ON emp.dept_id = dept.id

このクエリは、departmentsインデックスのすべてのドキュメントを返し、employeesインデックスに一致するドキュメントがあれば結合します。一致しない場合は、employees側のフィールドはNULLで埋められます。

これらのクエリをテストするために、employeesdepartmentsという2つのインデックスを作成し、データをインポートします。

インデックスemployeesの作成

PUT /`employees`
{
  "mappings": {
    "properties": {
      "emp_id": { "type": "integer" },
      "emp_name": { "type": "text" },
      "dept_id": { "type": "integer" }
    }
  }
}

インデックスdepartmentsの作成

PUT /`departments`
{
  "mappings": {
    "properties": {
      "id": { "type": "integer" },
      "dept_name": { "type": "text" }
    }
  }
}

employeesにデータをインポート

POST /`employees`/_bulk
{ "index": { "_id": 1 } }
{ "emp_id": 101, "emp_name": "佐藤 健", "dept_id": 1 }
{ "index": { "_id": 2 } }
{ "emp_id": 102, "emp_name": "田中 美咲", "dept_id": 2 }
{ "index": { "_id": 3 } }
{ "emp_id": 103, "emp_name": "鈴木 裕太", "dept_id": 1 }
{ "index": { "_id": 4 } }
{ "emp_id": 104, "emp_name": "渡辺 薫", "dept_id": 3 }
{ "index": { "_id": 5 } }
{ "emp_id": 105, "emp_name": "高橋 陽子", "dept_id": 99 }

departmentsにデータをインポート

POST /`departments`/_bulk
{ "index": { "_id": 1 } }
{ "id": 1, "dept_name": "営業部" }
{ "index": { "_id": 2 } }
{ "id": 2, "dept_name": "開発部" }
{ "index": { "_id": 6 } }
{ "id": 6, "dept_name": "広報部" }

データインポート後、SQLを使用してこれらのクエリを実行します。

1. サブクエリ

POST /_sql
{
  "query": "SELECT * FROM `employees` WHERE dept_id IN (SELECT id FROM `departments`)"
}

2. 内部結合

POST /_sql
{
  "query": "SELECT emp.emp_name, dept.dept_name FROM `employees` emp JOIN `departments` dept ON emp.dept_id = dept.id"
}

3. 左外部結合

POST /_sql
{
  "query": "SELECT emp.emp_name, dept.dept_name FROM `employees` emp LEFT JOIN `departments` dept ON emp.dept_id = dept.id"
}

4. 右外部結合

POST /_sql
{
  "query": "SELECT emp.emp_name, dept.dept_name FROM `employees` emp RIGHT JOIN `departments` dept ON emp.dept_id = dept.id"
}

SQLによる全文検索

Easysearchにおけるmatchクエリとmatch_phraseクエリは、全文検索で異なる用途を持ちます。

  1. matchクエリ:
    • ドキュメントに対する一般的な全文検索に使用されます。
    • 検索キーワードはテキスト解析器によって語彙に分割され、これらの語彙に対して検索が実行されます。
    • 単一または複数のフィールドに対して利用でき、"AND"や"OR"などのブーリアン演算子もサポートします。
    • 例: "Easysearchは強力"を検索すると、"Easysearch", "は", "強力"の各語彙に分割され、これらの語彙のいずれかを含むドキュメントがマッチと見なされます。
    {
      "query": {
        "match": {
          "text_content": "Easysearchは強力なツール"
        }
      }
    }
    
  2. match_phraseクエリ:
    • フレーズ検索に使用されます。
    • 検索フレーズがドキュメント内に正確に、かつ同じ順序と間隔で出現することを要求します。
    • 正確なフレーズマッチが必要なシナリオに適しています。
    • 例: "Easysearchは強力なツール"を検索すると、この正確なフレーズを含むドキュメントのみがマッチと見なされます。
    {
      "query": {
        "match_phrase": {
          "text_content": "Easysearchは強力なツール"
        }
      }
    }
    

要約すると、matchは一般的なキーワード検索に柔軟に対応し、match_phraseは正確なフレーズの一致が求められる検索に利用されます。

SQL全文検索の例

まず、データを準備し、SQLを使って全文検索を実行します。

データのバルクインポート:

POST /blog_articles/_bulk
{ "index": { "_id": 1 } }
{ "article_id": 1, "text_content": "分散型検索エンジンのメリットとデメリットを解説します。" }
{ "index": { "_id": 2 } }
{ "article_id": 2, "text_content": "検索エンジンの最適化はSEOにとって重要です。" }
{ "index": { "_id": 3 } }
{ "article_id": 3, "text_content": "メリットを活かしてシステムを改善しましょう。" }
{ "index": { "_id": 4 } }
{ "article_id": 4, "text_content": "多くのシステムはメリットとデメリットを考慮して設計されます。" }
{ "index": { "_id": 5 } }
{ "article_id": 5, "text_content": "デメリットを理解することが成功の鍵です。" }
{ "index": { "_id": 6 } }
{ "article_id": 6, "text_content": "分散システムは複雑ですが大きなメリットがあります。" }

全文検索のSQLクエリの実行:

SELECT * FROM blog_articles;
SELECT * FROM blog_articles WHERE match(text_content, 'メリット');
SELECT * FROM blog_articles WHERE match_phrase(text_content, '検索エンジンの最適化');

タグ: Easysearch Elasticsearch Query DSL SQL 全文検索

5月15日 17:54 投稿