多字符串查詢
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }},
{ "bool": {
"should": [
{ "match": { "translator": "Constance Garnett" }},
{ "match": { "translator": "Louise Maude" }}
]
}}
]
}
}
}
4個match查詢,為什么后兩個使用bool包裹?
同一層查詢每條語句具有相同的權重!!!
語句的優先級
boost
單字符串查詢
用戶期望將所有的搜索項堆積到單個字段中。
1、最佳字段
例如我們搜索“es reindex”,對于title和body這樣的兩個字段,“es reindex”同時出現在一個字段時的文檔評分更高
2、多數字段
為了對相關度進行微調,常用的一個技術就是將相同的數據索引到不同的字段,使他們具有獨立的分析鏈。
主字段可能包括它們的詞源、同義詞以及變音詞或口音詞等,用來匹配盡可能多的文檔。
相同的文本被索引到其他字段,提供更精確的匹配。一個字段可以包括未經詞干提取的原詞;另一個字段包括口音,還有一個字段提供詞語相似性。
其他字段是作為匹配每個文檔時提高相關度評分的信號。
3、混合字段
對于某些實體,需要在多個字段中確認其信息,單個字段都只能作為整體的一部分:
Person: first_name、last_name
如同在Persion這個大字段中搜索 first_name和last_name這兩個字段
最佳字段
以兩個文檔為例
PUT /my_index/my_type/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /my_index/my_type/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
bool查詢,想要匹配Brown fox
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
我們想要的結果是文檔2,但是文檔1的評分更高
bool如何計算評分:
1、執行should語句中的兩個查詢。
2、兩個查詢的評分相加
3、乘以匹配語句的總數
4、除以所有語句總數(這里為2,指的應該兩個字段???)
文檔1中兩個字段都包含brown,所以兩個match語句都有一個評分。
文檔2中body字段包含兩個詞,獲得較高分,但title字段沒有包含任何詞,平均一下文檔2的整體分就比文檔1低。
可以使用dis_max分離最大化查詢Disjunction Max Query,大致意思是,將任何與任一查詢匹配的文檔作為結果返回,但只將最匹配的評分作為查詢的評分結果返回:
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
最佳字段查詢調優
dis_max查詢會單個匹配最佳字段,而忽略其他的匹配。(單個最佳匹配語句的評分作為整體評分)
可以通過指定tie_breaker字段將其他語句的評分也考慮其中。
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
結果如下:
{
"hits": [
{
"_id": "2",
"_score": 0.14757764, (1)
"_source": {
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_id": "1",
"_score": 0.124275915, (1)
"_source": {
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
}
]
}
不帶有tie_breaker參數時,評分1等于評分2
tie_breaker的評分方式:
1、獲得最佳匹配語句的評分
2、其他語句的評分結果乘以tie_breaker
3、上述相加即為最后評分
tie_breaker的合理值應該處于0.1-0.4之間
multi_match多匹配查詢
在多個字段上反復執行相同的查詢。
multi_match多匹配查詢的類型有多種,其中三種與前文的最佳字段(best_fields)、多數字段(most_fields)、跨字段相匹配(cross_fields)
默認情況查詢的類型是best_fields,例如:
{
"dis_max": {
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
{
"match": {
"body": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
],
"tie_breaker": 0.3
}
}
可以使用multi_match重寫成更簡潔的形式
{
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields", (1)
"fields": [ "title", "body" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%" (2)
}
}
minimum_should_match、operator 這樣的參數會被傳遞到生成的match查詢中
匹配的字段可以使用模糊匹配的方式給出,例如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
可以使用^
為單個字段提升權重
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ] (1)
}
}
多數字段
召回率:返回所有的相關文檔
精確率:不返回無關文檔
全文搜索的兩個名詞,目的是在結果的第一頁為用戶呈現最為相關的文檔
多字段映射,首先要做的事情就是對我們的字段索引兩次,一次使用詞干模式,一次使用非詞干模式
PUT /my_index
{
"settings": { "number_of_shards": 1 }, (1)
"mappings": {
"my_type": {
"properties": {
"title": { (2)
"type": "string",
"analyzer": "english",
"fields": {
"std": { (3)
"type": "string",
"analyzer": "standard"
}
}
}
}
}
}
}
簡單的查詢,兩個文檔的評分相同;只查詢title.std那么只有文檔2是匹配的
GET /my_index/_search
{
"query": {
"match": {
"title": "jumping rabbits"
}
}
}
如果同時查詢兩個字段,然后使用bool查詢將結果合并,那么兩個文檔都是匹配的,文檔2的分更高
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields", (1)
"fields": [ "title", "title.std" ]
}
}
}
用廣度匹配字段title
匹配盡可能多的文檔,提升召回率
同時又使用title.std
作為信號將相關度更高的文檔置于結果頂部
跨字段實體搜索
多字符串查詢中,我們為每個字段使用不同的字符串,
而跨字段查詢是使用單個字符串在多個字段中進行搜索。
例如我們搜索Poland Street W1V
這個地址,它需要匹配多個字段
假如使用motst_fields會出現如下問題:
1、它是為多數字段匹配任意詞設計的,而不是在所有字段中找到最匹配的
2、它不能使用operator或minimum_should_match參數來降低相關結果造成的長尾效應
3、詞頻對于每個字段是不一樣的,而且它們之間的相互影響會導致不好的排序結果。
字段中心式查詢
most_fields(best_fields)出現的問題都是因為它是字段中心式查詢,而不是詞中心式的。
詞中心式:當真正感興趣的事匹配詞的時候,它為我們查找的是最匹配的字段
問題1:在多個字段中匹配相同的詞
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "Poland Street W1V",
"type": "most_fields",
"fields": [ "street", "city", "country", "postcode" ]
}
}
}
生成的explanation解釋:
(street:poland street:street street:w1v)
(city:poland city:street city:w1v)
(country:poland country:street country:w1v)
(postcode:poland postcode:street postcode:w1v)
可以發現兩個字段都與poland匹配的文檔要比一個字段同時匹配poland和street文檔的評分高(可能是順序,得出這樣的結論)
問題2:剪掉長尾
在精度匹配中,我們可以使用operator和minimum_should_match參數來消除結果中幾乎不相關的長尾,但是對于best_fields和most_fields中,這些參數會在match查詢生成時被傳入,例如操作符and,就會要求所有詞必須存在與相同字段中,這顯然是不對的
問題3:詞頻
{
"query": {
"multi_match": {
"query": "Peter Smith",
"type": "most_fields",
"fields": [ "*_name" ]
}
}
}
可能在結果中將“Smith Williams” 置于 “Peter Smith” 之上。
簡單來說就是smith在名字段中具有較高的IDF,他會削弱“Peter”作為名和“smith”作為姓時低IDF的所起作用。
解決方案
存在這些問題僅僅是因為我們在處理著多個字段,如果將這些字段組合成單個字段,問題就會消失。
{
"first_name": "Peter",
"last_name": "Smith",
"full_name": "Peter Smith"
}
這樣只要查詢full_name剛才的問題就會消失,但是數據卻會冗余。取而代之的是另外兩種方案,一個是在索引時,另一個是在搜索時
自定義_all字段
_all字段的索引方式是將所有其他字段的值作為一個大字符串索引的。然而這么做并不靈活,為了靈活,我們可以給人名添加一個自定義_all字段,也為地址添加另一個_all字段
可以使用copy_to參數來實現這個功能
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name" (1)
},
"last_name": {
"type": "string",
"copy_to": "full_name" (1)
},
"full_name": {
"type": "string"
}
}
}
}
}
first_name和last_name字段的值會被復制到full_name中
copy_to設置對multi_field無效。
多字段只是以不同方式簡單索引“主”字段,他們沒有自己的數據源,只要對主字段copy_to就能輕而易舉的達到相同的效果
PUT /my_index
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name", (1)
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
},
"full_name": {
"type": "string"
}
}
}
}
}
跨字段查詢
搜索時相應的解決方案:使用cross_fields進行multi_match查詢。
cross_fields是詞中心式的查詢,它將所有字段當成一個大字段,并在每個字段中查詢每個詞
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "most_fields",
"operator": "and", (1)
"fields": [ "first_name", "last_name" ]
}
}
}
得到結果表示peter和smith都必須同時出現在相同字段中,要么是first_name,要么last_name
(+first_name:peter +first_name:smith)
(+last_name:peter +last_name:smith)
然而詞中心式會使用以下邏輯,意味著peter和smith都必須出現,但是可以出現在任意字段中
+(first_name:peter last_name:peter)
+(first_name:smith last_name:smith)
cross_fields類型首先分析查詢字符串并生成一個詞列表,然后它從所有字段中依次搜索每個詞。
這種不同的查詢方式自然就解決了前兩個問題。
至于IDF問題,它通過混合不同字段逆向索引文檔頻率的方式解決了詞頻的問題。
GET /_validate/query?explain
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields", (1)
"operator": "and",
"fields": [ "first_name", "last_name" ]
}
}
}
+blended("peter", fields: [first_name, last_name])
+blended("smith", fields: [first_name, last_name])
換句話說,它會在兩個字段字段中查找smith的IDF,然后使用最小值作為兩個字段的IDF。
Tip:為了讓cross_fields查詢以最優的方式工作,所有的字段都必須使用相同的分析器,具有相同分析器的字段會被分組在一起作為混合字段使用。
如果包括了不同分析器的字段,它們會以best_fields的相同方式加入到查詢結果中。
例如將title字段加入到之前的查詢,explanation解釋如下:
(+title:peter +title:smith)
(
+blended("peter", fields: [first_name, last_name])
+blended("smith", fields: [first_name, last_name])
)
當在使用minimum_should_match和operator參數時,這點尤為重要
按字段提高權重
使用cross_fields和_all相比,其中一個優勢就是它可以在搜索時為單個字段提升權重
正如前文提到的^
自定義單字段查詢能否優于多字段查詢,取決于在多字段與單字段自定義_all之間代價的權衡。
Exact-Value精確值字段
將not_analyzed字段與 multi_match中analyzed字段混在一起沒有多大用處。
如前文的查詢,假如title是not_analyzed
title:peter smith
(
blended("peter", fields: [first_name, last_name])
blended("smith", fields: [first_name, last_name])
)
es會將peter smith完整的字符串作為查詢條件來搜索,所以需要在multi_match查詢中避免使用not_analyzed字段