序文
- この記事はダークホースから学び、重要な内容は著者の個人的な実践後に整理されます
- 提案: もっと実践的に、もっと実践的に! !
DSL クエリ ドキュメント
1.1 DSL クエリの分類
-
Elasticsearch は、クエリを定義するための JSON ベースの DSL (ドメイン固有言語) を提供します。一般的なクエリの種類は次のとおりです。
-
すべてクエリ: 一般的なテストのために、すべてのデータをクエリします。例: match_all
-
全文検索 (全文) クエリ: 単語セグメンターを使用してユーザー入力コンテンツをセグメント化し、転置インデックス データベースで照合します。例: match_query、multi_match_query
-
正確なクエリ: 正確な入力値に基づいてデータを検索します。通常は、キーワード、数値、日付、ブール値、およびその他のタイプのフィールドを検索します。例: ID、範囲、用語
-
地理 (地理) クエリ: 緯度と経度に基づくクエリ。例: geo_ distance、 geo_bounding_box
-
複合(compound)クエリ:複合クエリは、上記の様々なクエリ条件を組み合わせたり、クエリ条件をマージしたりすることができます。例: bool、function_score
- クエリ構文は基本的に同じです。
GET /indexName/_search { "query": { "查询类型": { "查询条件": "条件值" } } }
- すべてのコンテンツをクエリします: クエリ タイプは match_all && クエリ条件はありません
# 查询所有 GET /indexName/_search { "query": { "match_all": { } } }
1.2 全文検索クエリ
- 全文検索クエリの基本プロセス:
- 検索されたコンテンツを分割してエントリを取得する
- 転置インデックスライブラリ内の一致するエントリに従って、ドキュメントIDを取得します
- ドキュメントIDに従ってドキュメントを検索し、ユーザーに返します。
- 用語は照合に使用されるため、検索に参加するフィールドもセグメント化できるテキスト タイプのフィールドである必要があります。
- 一般的な全文検索クエリには次のようなものがあります。
match
クエリ: 単一フィールドのクエリmulti_match
クエリ: 複数フィールドのクエリ、どのフィールドでも条件を満たすことができます
- 一致クエリの構文は次のとおりです。
GET /indexName/_search { "query": { "match": { "FIELD": "TEXT" } } } #match查询示例 GET /hotel/_search { "query": { "match": { "all": "外滩如家" } } }
- mulit_match 構文は次のとおりです。
GET /indexName/_search { "query": { "multi_match": { "query": "TEXT", "fields": ["FIELD1", " FIELD12"] } } } #multi_match查询示例 GET /hotel/_search { "query": { "multi_match": { "query": "外滩如家", "fields": ["brand","name","business"] } } }
-
copy_to
パラメーターを使用すると、複数のフィールドの値をグループ フィールドにコピーし、単一のフィールドとしてクエリできるようになります。PUT /test { "mappings": { "properties": { "first_name": { "type": "text", "copy_to": "full_name" }, "last_name": { "type": "text", "copy_to": "full_name" }, "full_name": { "type": "text" } } } } PUT test/_doc/1 { "first_name": "shanghai", "last_name": "beijing" } GET test/_search { "query": { "match": { "full_name": { "query": "shanghai beijing", "operator": "and" } } } }
-
copy_to
用語ではなくフィールド値です (分析プロセスによって生成されます) -
同じ値を複数のフィールドにコピーするには、次を使用します。
"copy_to": [ "field_1", "field_2" ]
-
検索フィールドが多いほど、クエリのパフォーマンスへの影響が大きくなるため、copy_to を使用してから単一フィールド クエリを使用することをお勧めします。
1.3 正確なクエリ
- 正確なクエリは通常、キーワード、値、日付、ブール値、およびその他のタイプのフィールドを検索するものであり、検索条件を分割しません。一般的なものは次のとおりです。
- term: 用語の正確な値に基づいたクエリ
- range: 値の範囲に基づいてクエリを実行します。
- 用語クエリ
- 完全一致クエリのフィールド検索は単語分割のないフィールドであるため、クエリ条件も単語分割のないエントリである必要があります。クエリの際、入力された内容は自動値と完全に一致する場合にのみ条件を満たしているとみなされます。ユーザーが入力した内容が多すぎると、データを検索できなくなります。
- 文法の説明:
// term查询 GET /indexName/_search { "query": { "term": { "FIELD": { "value": "VALUE" } } } }
- 範囲クエリ
- 範囲クエリ。通常は数値型の範囲フィルタリングに使用されます。
- 基本的な構文:
// range查询 GET /indexName/_search { "query": { "range": { "FIELD": { "gte": 10, // 这里的gte代表大于等于,gt则代表大于 "lte": 20 // lte代表小于等于,lt则代表小于 } } } }
1.4 地理座標クエリ
- 地理座標のクエリは経度と緯度のクエリ、公式文書に基づいています
- 長方形範囲クエリ
- 長方形範囲クエリ [
geo_bounding_box
クエリ]、座標が特定の長方形範囲内にあるすべてのドキュメントをクエリします。 - クエリを実行する場合は、長方形の左上と右下の点の座標を指定してから長方形を描画する必要があり、その長方形内にあるすべての点が条件を満たします。
- 文法の説明
// geo_bounding_box查询 GET /indexName/_search { "query": { "geo_bounding_box": { "FIELD": { "top_left": { // 左上点“ "lat": 31.1, "lon": 121.5 }, "bottom_right": { // 右下点 "lat": 30.9, "lon": 121.7 } } } } }
- 長方形範囲クエリ [
- 近くのクエリ
- 近傍クエリ [距離クエリ (geo_ distance)]: 指定された中心点が特定の距離値未満であるすべてのドキュメントをクエリします。
- 文法の説明:
// geo_distance 查询 GET /indexName/_search { "query": { "geo_distance": { "distance": "15km", // 半径 "FIELD": "31.21,121.5" // 圆心 } } }
- 近傍クエリ [距離クエリ (geo_ distance)]: 指定された中心点が特定の距離値未満であるすべてのドキュメントをクエリします。
1.5 複合クエリ
- 複合クエリ: 他の単純なクエリを組み合わせて、より複雑な検索ロジックを実装します。一般的なものは 2 つあります。
fuction score
: 計算関数クエリ。ドキュメントの関連性の計算を制御し、ドキュメントのランキングを制御できます。bool query
: ブール クエリ。論理関係を使用して他の複数のクエリを結合し、複雑な検索を実現します。
- 一致クエリを使用すると、検索語との関連度に応じてドキュメント結果にスコア (_score) が付けられ、返された結果はスコアの降順に並べ替えられます。
- スコアリングアルゴリズム:
- 初期の elasticsearch では、使用されたスコアリング アルゴリズムは TF-IDF アルゴリズムでした。
- 後のバージョン 5.1 アップグレードでは、elasticsearch はアルゴリズムを BM25 アルゴリズムに改善しました。
- さらに詳しく知りたい方は「史上最小ホワイトBM25の徹底解説と実現」をご覧ください。
- 初期の elasticsearch では、使用されたスコアリング アルゴリズムは TF-IDF アルゴリズムでした。
- TF-IDF アルゴリズムには欠陥があります。つまり、用語の頻度が高くなるほどドキュメント スコアが高くなり、単一の用語がドキュメントに与える影響が大きくなります。ただし、BM25 には 1 つのエントリのスコアに上限があり、曲線はより滑らかになります。
1.5.1 計算関数のクエリ
-
関連性に基づくスコア付けは合理的な要件ですが、必ずしも合理的な要件がプロダクト マネージャーに必要なわけではありません。
-
文法の説明
-
関数スコア クエリには 4 つの部分が含まれます。
- 元のクエリ条件: クエリ部分。この条件に基づいてドキュメントを検索し、BM25 アルゴリズムに基づいてドキュメントをスコア付けします。元のスコア(クエリ スコア)
- フィルター条件: フィルター部分。この条件を満たすドキュメントが再計算されます。
- 計算機能: フィルタ条件を満たすドキュメントをこの関数に従って計算する必要があり、得られた関数スコア(関数スコア) には 4 つの関数があります
- 重み: 関数の結果は定数です
- field_value_factor: ドキュメント内のフィールド値を関数の結果として使用します。
- random_score: 関数の結果として乱数を使用します。
- script_score: カスタムスコアリング関数のアルゴリズム
- 計算モード: 計算関数の結果、元のクエリの相関計算スコア、および 2 つの間の計算方法。以下が含まれます。
- 乗算: 乗算
- replace: クエリスコアを関数スコアに置き換えます
- その他: 合計、平均、最大、最小
-
関数スコアの演算プロセスは次のとおりです。
- 元の条件に従ってドキュメントをクエリおよび検索し、元のスコア(クエリ スコア)と呼ばれる関連性スコアを計算します。
- フィルタ基準に基づいてドキュメントをフィルタリングする
- フィルタ条件を満たした文書を、計算関数の計算に基づいて関数スコア(関数スコア)を取得します。
- 動作モードに基づいてオリジナルスコア(クエリスコア)と関数スコア(関数スコア)が計算され、最終的な結果が相関スコアとして得られます。
-
キーポイント:
- フィルタ条件: どのドキュメントのスコアが変更されたかを決定します
- スコアリング関数: 関数のスコアを決定するアルゴリズム
- 計算モード: 最終的な計算結果を決定する
-
デモ
GET /hotel/_search { "query": { "function_score": { "query": { "match": { "all": "外滩" } }, "functions": [ #算分函数 { "filter": { "term": { "brand": "如家" # 过滤条件 } }, "weight": 10 # 算分权重 } ], "boost_mode": "sum" # 加权模式 } } }
1.5.2 ブールクエリ
- ブール クエリは、それぞれがサブクエリである 1 つ以上のクエリ句の組み合わせです。サブクエリは次の方法で組み合わせることができます。
- must: 「and」と同様に、各サブクエリと一致する必要があります。
- should: 「or」に似た選択的一致サブクエリ
- must_not: 一致してはなりません。スコアリングに参加しません。「not」と同様です。
- フィルタ: 一致する必要があります。スコアリングには参加しません
- 注: 検索時、スコアリングに関与するフィールドが増えるほど、クエリのパフォーマンスは低下します。したがって、複数の条件を指定してクエリを実行する場合は、次のことをお勧めします。
- 検索ボックスのキーワード検索は全文検索クエリであり、must クエリを使用し、スコアリングに参加します。
- その他のフィルター条件については、フィルター クエリを使用します。採点には参加しない
- デモ
#bool查询 GET /hotel/_search { "query": { "bool": { "must": [ { "term": { "city": "上海"} } ], "should": [ { "term":{ "brand": "皇冠假日" }}, { "term":{ "brand": "华美达"}} ], "must_not": [ { "range": { "price": { "lte": 500 } }} ], "filter": [ { "range": { "score": { "gte": 45 } }} ] } } }
2 つの結果の処理
- 検索結果は、ユーザーが指定した方法で処理または表示できます。
2.1 並べ替え
2.1.1 通常のフィールドの並べ替え
- Elasticsearch はデフォルトで相関スコア (_score) に従って並べ替えますが、検索結果を並べ替えるカスタム方法もサポートしています。並べ替え可能なフィールド タイプには、キーワード タイプ、数値タイプ、地理座標タイプ、日付タイプなどが含まれます。
GET /indexName/_search { "query": { "match_all": { } }, "sort": [ { "FIELD": "desc" // 排序字段、排序方式ASC、DESC } ] }
- ソート条件は配列であり、複数のソート条件を記述することができます。宣言の順序に従って、最初の条件が等しい場合は、2 番目の条件に従って並べ替えます。
- デモ: ホテル データをユーザー評価 (スコア) の降順に並べ替え、ホテル データが同じ評価の場合は価格 (価格) の昇順に並べ替えます。
#排序 GET /hotel/_search { "query": { "match_all": { } }, "sort": [ { "score": { "order": "desc" }, "price": { "order": "asc" } } ] }
2.1.2 地理座標による並べ替え
- 文法説明:
GET /indexName/_search { "query": { "match_all": { } }, "sort": [ { "_geo_distance" : { "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点 "order" : "asc", // 排序方式 "unit" : "km" // 排序的距离单位 } } ] }
- 対象点として座標を指定し、各ドキュメント内の指定フィールド(geo_point型である必要がある)の座標から対象点までの距離を計算し、その距離に応じて並べ替えます。
2.2 ページネーション
- Elasticsearch はデフォルトで上位 10 データのみを返します。さらに多くのデータをクエリする場合は、ページング パラメーターを変更する必要があります。
- elasticsearch では、返されるページング結果は、from および size パラメータを変更することで制御されます: from: どのドキュメントから開始するか、size: 合計でクエリするドキュメントの数
2.2.1 基本的なページネーション
- ページネーションの基本的な構文は次のとおりです。
GET /hotel/_search { "query": { "match_all": { } }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ { "price": "asc"} ] }
2.2.2 ディープページングの問題
- ここで 990 ~ 1000 のデータをクエリする場合、クエリ ロジックは次のように記述する必要があります。
GET /hotel/_search { "query": { "match_all": { } }, "from": 990, // 分页开始的位置,默认为0 "size": 10, // 期望获取的文档总数 "sort": [ { "price": "asc"} ] }
- ここではクエリ 990 から始まるデータ、つまり990 番目→ 1000 番目 990\to 1000 番目です。第990→1000 番目のデータ。ただし、elasticsearch 内でページングする場合は、最初に0 → 1000 0\to10000→1000個、そのうち990 1000 990 ~ 1000個を990 1000 の場合は次の 10 個
- ES は分散されるため、ディープ ページングの問題に直面します。たとえば、価格で並べ替えた後、from = 990、size = 10 のデータを取得します。
- まず、各データ シャードの最初の 1000 ドキュメントを並べ替えてクエリします。
- 次に、すべてのノードの結果を集計し、メモリ内で並べ替えて最初の 1000 個のドキュメントを選択します。
- 最後に、1000件の中から990件から10件の文書を選択します。
- 検索ページの数が深すぎる場合、または結果セット (+ サイズ) が大きい場合、メモリと CPU の消費量が高くなります。したがって、ES は結果セット クエリの上限を 10000 に設定します。
- ディープ ページングに対して、ES は 2 つのソリューションを提供します
- search after: ページング時に並べ替えが必要です。原則として、最後の並べ替え値から開始してデータの次のページをクエリします。[お勧め]
- スクロール: 原則は、並べ替えられたデータのスナップショットを作成し、メモリに保存することです。[非推奨]
2.3 概要
ページング クエリの一般的な実装 | アドバンテージ | 欠点がある | 使用するシーン |
---|---|---|---|
from + size |
ランダムなページめくりをサポート | ディープ ページングの問題、デフォルトのクエリ上限 (from + サイズ) は 10000 | Baidu、JD.com、Google、Taobao などのランダムなページめくり検索 |
after search |
クエリの上限なし(1つのクエリのサイズは10000を超えない) | ページごとに逆方向のクエリのみが可能で、ランダムなページめくりはサポートされていません | 携帯電話で下にスクロールしてページをめくるなど、ランダムなページめくりの要件なしで検索します。 |
scroll |
クエリの上限なし(1つのクエリのサイズは10000を超えない) | 追加のメモリ消費が発生し、検索結果はリアルタイムではありません。 | 大量のデータの取得と移行。ES7.1 以降は推奨されません。検索後のソリューションを使用することをお勧めします。 |
2.4 ハイライト
- ハイライトの実装は 2 つのステップに分かれています。
<em>
ドキュメント内のすべてのキーワードにラベルを追加します。- ページはタグの
<em>
CSS スタイルを書き込みます
- 構文の強調表示:
GET /hotel/_search { "query": { "match": { "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询 } }, "highlight": { "fields": { // 指定要高亮的字段 "FIELD": { "pre_tags": "<em>", // 用来标记高亮字段的前置标签 "post_tags": "</em>" // 用来标记高亮字段的后置标签 } } } }
- 知らせ:
- ハイライト表示はキーワードを対象としているため、検索条件には範囲クエリではなくキーワードが含まれている必要があります。
- デフォルトでは、強調表示されたフィールドは検索で指定されたフィールドと同じである必要があります。それ以外の場合は強調表示できません。
- 非検索フィールドを強調表示したい場合は、属性を追加する必要があります: required_field_match=false
- デモ
# 实现高亮 GET /hotel/_search { "query": { "match": { "all": "如家" } }, "highlight": { "fields": { "name": { "require_field_match": "false", "pre_tags": "<em>", "post_tags": "</em>" } } } }
3 つの RestClient クエリ ドキュメント
3.1 クイックスタート
- コードの解釈:
- 最初のステップは、
SearchRequest
オブジェクトを作成し、インデックス ライブラリ名を指定することです。 request.source()
2 番目のステップは、クエリ、ページング、並べ替え、強調表示などを含む DSL の構築を使用することです。query()
QueryBuilders.matchAllQuery()
: DSLを使用して match_all クエリを構築するクエリ条件を表します。- 3 番目のステップは、 client.search() を使用してリクエストを送信し、レスポンスを取得することです。
- 最初のステップは、
- 主要な API:
request.source()
、クエリ、並べ替え、ページング、強調表示などのすべての機能が含まれています。QueryBuilders
、match、term、function_score、bool などのさまざまなクエリを含みます。
3.2 応答の解析
elasticsearch によって返される結果は JSON 文字列であり、その構造には次のものが含まれます。
hits
: ヒットの結果total
: エントリの総数。value は特定のエントリの合計値です。max_score
: すべての結果で最もスコアの高いドキュメントの関連性スコアhits
: 検索結果のドキュメントの配列。それぞれが json オブジェクトです。_source
: ドキュメント内の元のデータ、これも json オブジェクト
JSON 文字列をレイヤーごとに解析するプロセスは次のとおりです。
SearchHits
: response.getHits() を通じて取得されます。これは、JSON の最も外側のヒットであり、ヒットの結果を表します。SearchHits#getTotalHits().value
:情報の総数を取得SearchHits#getHits()
: ドキュメント配列である SearchHit 配列を取得します。SearchHit#getSourceAsString()
: ドキュメント結果の _source (元の JSON ドキュメント データ) を取得します。
- 完全なコード
@Test void testMatchAll() throws IOException { // 1.准备Request SearchRequest request = new SearchRequest("hotel"); // 2.准备DSL request.source() .query(QueryBuilders.matchAllQuery()); // 3.发送请求 SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 4.解析响应 handleResponse(response); } private void handleResponse(SearchResponse response) { // 4.解析响应 SearchHits searchHits = response.getHits(); // 4.1.获取总条数 long total = searchHits.getTotalHits().value; System.out.println("共搜索到" + total + "条数据"); // 4.2.文档数组 SearchHit[] hits = searchHits.getHits(); // 4.3.遍历 for (SearchHit hit : hits) { // 获取文档source String json = hit.getSourceAsString(); // 反序列化 HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class); System.out.println("hotelDoc = " + hotelDoc); } }
3.3 全文検索クエリ
- 全文検索のmatchクエリとmulti_matchクエリは基本的にmatch_allのAPIと同じです。違いは、クエリ部分であるクエリ条件です。QueryBuilders によって提供されるメソッドも使用します。
@Test
void testMatch() throws IOException {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
request.source()
.query(QueryBuilders.matchQuery("all", "如家"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
3.4 正確なクエリ
@Test
void testTerm() throws IOException {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
//request.source().query(QueryBuilders.termQuery("city", "杭州"));
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
3.5 ブールクエリ
- ブールクエリは、must、must_not、filter などを使用して他のクエリを組み合わせます。
@Test
void testBool() throws IOException {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.准备BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.2.添加term
boolQuery.must(QueryBuilders.termQuery("city", "上海"));
// 2.3.添加range
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQuery);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
3.6 並べ替えとページング
@Test
void testPageAndSort() throws IOException {
// 页码,每页大小
int page = 1, size = 3;
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
request.source().query(QueryBuilders.matchAllQuery());
// 2.2.排序 sort
request.source().sort("price", SortOrder.ASC);
// 2.3.分页 from、size
request.source().from((page - 1) * size).size(size);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
3.7 ハイライト
- ハイライト API には、要求 DSL 構築と結果解析の 2 つの部分が含まれています。まずリクエストの DSL 構造を見てみましょう。
- 強調表示された結果の処理は比較的面倒です。
@Test
void testHighlight() throws IOException {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query
request.source().query(QueryBuilders.matchQuery("all", "如家"));
// 2.2.高亮
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
//高亮的结果与查询的文档结果默认是分离的,并不在一起。
highlightResponse(response);
}
private static void highlightResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
//查询的总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
//查询的结果数组 文档数组
SearchHit[] hits = searchHits.getHits();
for(SearchHit hit:hits) {
//获取文档source
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(new Map[]{
highlightFields})) {
// 根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
// 获取高亮值
String name = highlightField.getFragments()[0].string();
// 覆盖非高亮结果
hotelDoc.setName(name);
}
}
System.out.println("hotelDoc="+hotelDoc);
}
}
結論: ダークホースツーリズムに関する提案
-
エッジブラウザを使用して Dark Horse の事例ページにアクセスする場合は、Web サイトのトラッキング保護をオフにすることをお勧めします
-
広告マークを追加する部分: 結果を確認する場合、まず広告マークを追加したホテル情報をクエリし、ホテルを検索するページに直接移動できます。