elasticsearch 請求體查詢
請求體查詢
簡易 查詢 —query-string search— 對於用命令列進行即席查詢(ad-hoc)是非常有用的。 然而,為了充分利用查詢的強大功能,你應該使用 請求體 search
API, 之所以稱之為請求體查詢(Full-Body Search),因為大部分引數是通過 Http 請求體而非查詢字串來傳遞的。
請求體查詢 —下文簡稱 查詢—不僅可以處理自身的查詢請求,還允許你對結果進行片段強調(高亮)、對所有或部分結果進行聚合分析,同時還可以給出 你是不是想找 的建議,這些建議可以引導使用者快速找到他想要的結果。
空查詢
讓我們以 最簡單的 search
API 的形式開啟我們的旅程,空查詢將返回所有索引庫(indices)中的所有文件:
GET /_search {}
這是一個空的請求體。 |
只用一個查詢字串,你就可以在一個、多個或者 _all
索引庫(indices)和一個、多個或者所有types中查詢:
GET /index_2014*/type1,type2/_search {}
同時你可以使用 from
和 size
引數來分頁:
GET /_search { "from": 30, "size": 10 }
一個帶請求體的 GET 請求?
某些特定語言(特別是 JavaScript)的 HTTP 庫是不允許
GET
請求帶有請求體的。 事實上,一些使用者對於GET
請求可以帶請求體感到非常的吃驚。而事實是這個RFC文件 RFC 7231— 一個專門負責處理 HTTP 語義和內容的文件 — 並沒有規定一個帶有請求體的
GET
請求應該如何處理!結果是,一些 HTTP 伺服器允許這樣子,而有一些 — 特別是一些用於快取和代理的伺服器 — 則不允許。對於一個查詢請求,Elasticsearch 的工程師偏向於使用
GET
方式,因為他們覺得它比POST
能更好的描述資訊檢索(retrieving information)的行為。然而,因為帶請求體的GET
請求並不被廣泛支援,所以search
API 同時支援POST
請求:POST /_search { "from": 30, "size": 10 }類似的規則可以應用於任何需要帶請求體的
GET
API。
我們將在聚合 聚合 章節深入介紹聚合(aggregations),而現在,我們將聚焦在查詢。
相對於使用晦澀難懂的查詢字串的方式,一個帶請求體的查詢允許我們使用 查詢領域特定語言(query domain-specific language) 或者 Query DSL 來寫查詢語句。
查詢表示式編輯
查詢表示式(Query DSL)是一種非常靈活又富有表現力的 查詢語言。 Elasticsearch 使用它可以以簡單的 JSON 介面來展現 Lucene 功能的絕大部分。在你的應用中,你應該用它來編寫你的查詢語句。它可以使你的查詢語句更靈活、更精確、易讀和易除錯。
要使用這種查詢表示式,只需將查詢語句傳遞給 query
引數:
GET /_search { "query": YOUR_QUERY_HERE }
空查詢(empty search) —{}
— 在功能上等價於使用 match_all
查詢, 正如其名字一樣,匹配所有文件:
GET /_search { "query": { "match_all": {} } }
查詢語句的結構編輯
一個查詢語句 的典型結構:
{ QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } }
如果是針對某個欄位,那麼它的結構如下:
{ QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } }
舉個例子,你可以使用 match
查詢語句 來查詢 tweet
欄位中包含 elasticsearch
的 tweet:
{ "match": { "tweet": "elasticsearch" } }
完整的查詢請求如下:
GET /_search { "query": { "match": { "tweet": "elasticsearch" } } }
合併查詢語句
查詢語句(Query clauses) 就像一些簡單的組合塊 ,這些組合塊可以彼此之間合併組成更復雜的查詢。這些語句可以是如下形式:
- 葉子語句(Leaf clauses) (就像
match
語句) 被用於將查詢字串和一個欄位(或者多個欄位)對比。 - 複合(Compound) 語句 主要用於 合併其它查詢語句。 比如,一個
bool
語句 允許在你需要的時候組合其它語句,無論是must
匹配、must_not
匹配還是should
匹配,同時它可以包含不評分的過濾器(filters):
{ "bool": { "must": { "match": { "tweet": "elasticsearch" }}, "must_not": { "match": { "name": "mary" }}, "should": { "match": { "tweet": "full text" }}, "filter": { "range": { "age" : { "gt" : 30 }} } } }
一條複合語句可以合併 任何 其它查詢語句,包括複合語句,瞭解這一點是很重要的。這就意味著,複合語句之間可以互相巢狀,可以表達非常複雜的邏輯。
例如,以下查詢是為了找出信件正文包含 business opportunity
的星標郵件,或者在收件箱正文包含business opportunity
的非垃圾郵件:
{ "bool": { "must": { "match": { "email": "business opportunity" }}, "should": [ { "match": { "starred": true }}, { "bool": { "must": { "match": { "folder": "inbox" }}, "must_not": { "match": { "spam": true }} }} ], "minimum_should_match": 1 } }
到目前為止,你不必太在意這個例子的細節,我們會在後面詳細解釋。最重要的是你要理解到,一條複合語句可以將多條語句 — 葉子語句和其它複合語句 — 合併成一個單一的查詢語句。
查詢與過濾編輯
Elasticsearch 使用的查詢語言(DSL) 擁有一套查詢元件,這些元件可以以無限組合的方式進行搭配。這套元件可以在以下兩種情況下使用:過濾情況(filtering context)和查詢情況(query context)。
當使用於 過濾情況 時,查詢被設定成一個“不評分”或者“過濾”查詢。即,這個查詢只是簡單的問一個問題:“這篇文件是否匹配?”。回答也是非常的簡單,yes 或者 no ,二者必居其一。
created
時間是否在2013
與2014
這個區間?status
欄位是否包含published
這個單詞?lat_lon
欄位表示的位置是否在指定點的10km
範圍內?
當使用於 查詢情況 時,查詢就變成了一個“評分”的查詢。和不評分的查詢類似,也要去判斷這個文件是否匹配,同時它還需要判斷這個文件匹配的有 _多好_(匹配程度如何)。 此查詢的典型用法是用於查詢以下文件:
- 查詢與
full text search
這個詞語最佳匹配的文件 - 包含
run
這個詞,也能匹配runs
、running
、jog
或者sprint
- 包含
quick
、brown
和fox
這幾個詞 — 詞之間離的越近,文件相關性越高 - 標有
lucene
、search
或者java
標籤 — 標籤越多,相關性越高
一個評分查詢計算每一個文件與此查詢的 _相關程度_,同時將這個相關程度分配給表示相關性的欄位 `_score`,並且按照相關性對匹配到的文件進行排序。這種相關性的概念是非常適合全文搜尋的情況,因為全文搜尋幾乎沒有完全 “正確” 的答案。
自 Elasticsearch 問世以來,查詢與過濾(queries and filters)就獨自成為 Elasticsearch 的元件。但從 Elasticsearch 2.0 開始,過濾(filters)已經從技術上被排除了,同時所有的查詢(queries)擁有變成不評分查詢的能力。
然而,為了明確和簡單,我們用 "filter" 這個詞表示不評分、只過濾情況下的查詢。你可以把 "filter" 、 "filtering query" 和 "non-scoring query" 這幾個詞視為相同的。
相似的,如果單獨地不加任何修飾詞地使用 "query" 這個詞,我們指的是 "scoring query" 。
效能差異
過濾查詢(Filtering queries)只是簡單的檢查包含或者排除,這就使得計算起來非常快。考慮到至少有一個過濾查詢(filtering query)的結果是 “稀少的”(很少匹配的文件),並且經常使用不評分查詢(non-scoring queries),結果會被快取到記憶體中以便快速讀取,所以有各種各樣的手段來優化查詢結果。
相反,評分查詢(scoring queries)不僅僅要找出 匹配的文件,還要計算每個匹配文件的相關性,計算相關性使得它們比不評分查詢費力的多。同時,查詢結果並不快取。
多虧倒排索引(inverted index),一個簡單的評分查詢在匹配少量文件時可能與一個涵蓋百萬文件的filter表現的一樣好,甚至會更好。但是在一般情況下,一個filter 會比一個評分的query效能更優異,並且每次都表現的很穩定。
過濾(filtering)的目標是減少那些需要通過評分查詢(scoring queries)進行檢查的文件。
如何選擇查詢與過濾
通常的規則是,使用 查詢(query)語句來進行 全文 搜尋或者其它任何需要影響 相關性得分 的搜尋。除此以外的情況都使用過濾(filters)。
最重要的查詢
雖然 Elasticsearch 自帶了很多的查詢,但經常用到的也就那麼幾個。我們將在 深入搜尋 章節詳細討論那些查詢的細節,接下來我們對最重要的幾個查詢進行簡單介紹。
match_all 查詢
match_all
查詢簡單的 匹配所有文件。在沒有指定查詢方式時,它是預設的查詢:
{ "match_all": {}}
它經常與 filter 結合使用--例如,檢索收件箱裡的所有郵件。所有郵件被認為具有相同的相關性,所以都將獲得分值為 1
的中性 `_score`。
match 查詢
無論你在任何欄位上進行的是全文搜尋還是精確查詢,match
查詢是你可用的標準查詢。
如果你在一個全文欄位上使用 match
查詢,在執行查詢前,它將用正確的分析器去分析查詢字串:
{ "match": { "tweet": "About Search" }}
如果在一個精確值的欄位上使用它, 例如數字、日期、布林或者一個 not_analyzed
字串欄位,那麼它將會精確匹配給定的值:
{ "match": { "age": 26 }} { "match": { "date": "2014-09-01" }} { "match": { "public": true }} { "match": { "tag": "full_text" }}
對於精確值的查詢,你可能需要使用 filter 語句來取代 query,因為 filter 將會被快取。接下來,我們將看到一些關於 filter 的例子。
不像我們在 輕量 搜尋 章節介紹的字串查詢(query-string search), match
查詢不使用類似 +user_id:2 +tweet:search
的查詢語法。它只是去查詢給定的單詞。這就意味著將查詢欄位暴露給你的使用者是安全的;你需要控制那些允許被查詢欄位,不易於丟擲語法異常。
multi_match 查詢
multi_match
查詢可以在多個欄位上執行相同的 match
查詢:
{ "multi_match": { "query": "full text search", "fields": [ "title", "body" ] } }
range 查詢
range
查詢找出那些落在指定區間內的數字或者時間:
{ "range": { "age": { "gte": 20, "lt": 30 } } }
被允許的操作符如下:
gt
大於
gte
大於等於
lt
小於
lte
小於等於
term 查詢
term
查詢被用於精確值 匹配,這些精確值可能是數字、時間、布林或者那些 not_analyzed
的字串:
{ "term": { "age": 26 }} { "term": { "date": "2014-09-01" }} { "term": { "public": true }} { "term": { "tag": "full_text" }}
term
查詢對於輸入的文字不 分析 ,所以它將給定的值進行精確查詢。
terms 查詢
terms
查詢和 term
查詢一樣,但它允許你指定多值進行匹配。如果這個欄位包含了指定值中的任何一個值,那麼這個文件滿足條件:
{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}
和 term
查詢一樣,terms
查詢對於輸入的文字不分析。它查詢那些精確匹配的值(包括在大小寫、重音、空格等方面的差異)。
exists 查詢和 missing 查詢
exists
查詢和 missing
查詢被用於查詢那些指定欄位中有值 (exists
) 或無值 (missing
) 的文件。這與SQL中的 IS_NULL
(missing
) 和 NOT IS_NULL
(exists
) 在本質上具有共性:
{ "exists": { "field": "title" } }
這些查詢經常用於某個欄位有值的情況和某個欄位缺值的情況。
組合多查詢
現實的查詢需求從來都沒有那麼簡單;它們需要在多個欄位上查詢多種多樣的文字,並且根據一系列的標準來過濾。為了構建類似的高階查詢,你需要一種能夠將多查詢組合成單一查詢的查詢方法。
你可以用 bool
查詢來實現你的需求。這種查詢將多查詢組合在一起,成為使用者自己想要的布林查詢。它接收以下引數:
must
文件 必須 匹配這些條件才能被包含進來。
must_not
文件 必須不 匹配這些條件才能被包含進來。
should
如果滿足這些語句中的任意語句,將增加 _score
,否則,無任何影響。它們主要用於修正每個文件的相關性得分。
filter
必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文件。
由於這是我們看到的第一個包含多個查詢的查詢,所以有必要討論一下相關性得分是如何組合的。每一個子查詢都獨自地計算文件的相關性得分。一旦他們的得分被計算出來, bool
查詢就將這些得分進行合併並且返回一個代表整個布林操作的得分。
下面的查詢用於查詢 title
欄位匹配 how to make millions
並且不被標識為 spam
的文件。那些被標識為 starred
或在2014之後的文件,將比另外那些文件擁有更高的排名。如果 _兩者_ 都滿足,那麼它排名將更高:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } }
如果沒有 must
語句,那麼至少需要能夠匹配其中的一條 should
語句。但,如果存在至少一條 must
語句,則對 should
語句的匹配沒有要求。
增加帶過濾器(filtering)的查詢
如果我們不想因為文件的時間而影響得分,可以用 filter
語句來重寫前面的例子:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }} ], "filter": { "range": { "date": { "gte": "2014-01-01" }} } } }
range 查詢已經從 |
通過將 range 查詢移到 filter
語句中,我們將它轉成不評分的查詢,將不再影響文件的相關性排名。由於它現在是一個不評分的查詢,可以使用各種對 filter 查詢有效的優化手段來提升效能。
所有查詢都可以借鑑這種方式。將查詢移到 bool
查詢的 filter
語句中,這樣它就自動的轉成一個不評分的 filter 了。
如果你需要通過多個不同的標準來過濾你的文件,bool
查詢本身也可以被用做不評分的查詢。簡單地將它放置到 filter
語句中並在內部構建布林邏輯:
{ "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }} ], "filter": { "bool": { "must": [ { "range": { "date": { "gte": "2014-01-01" }}}, { "range": { "price": { "lte": 29.99 }}} ], "must_not": [ { "term": { "category": "ebooks" }} ] } } } }
將 |
通過混合布林查詢,我們可以在我們的查詢請求中靈活地編寫 scoring 和 filtering 查詢邏輯。
constant_score 查詢
儘管沒有 bool
查詢使用這麼頻繁,constant_score
查詢也是你工具箱裡有用的查詢工具。它將一個不變的常量評分應用於所有匹配的文件。它被經常用於你只需要執行一個 filter 而沒有其它查詢(例如,評分查詢)的情況下。
可以使用它來取代只有 filter 語句的 bool
查詢。在效能上是完全相同的,但對於提高查詢簡潔性和清晰度有很大幫助。
{ "constant_score": { "filter": { "term": { "category": "ebooks" } } } }
|
驗證查詢
查詢可以變得非常的複雜,尤其 和不同的分析器與不同的欄位對映結合時,理解起來就有點困難了。不過 validate-query
API 可以用來驗證查詢是否合法。
GET /gb/tweet/_validate/query { "query": { "tweet" : { "match" : "really powerful" } } }
以上 validate
請求的應答告訴我們這個查詢是不合法的:
{ "valid" : false, "_shards" : { "total" : 1, "successful" : 1, "failed" : 0 } }
理解錯誤資訊
為了找出 查詢不合法的原因,可以將 explain
引數 加到查詢字串中:
GET /gb/tweet/_validate/query?explain { "query": { "tweet" : { "match" : "really powerful" } } }
|
很明顯,我們將查詢型別(match
)與欄位名稱 (tweet
)搞混了:
{ "valid" : false, "_shards" : { ... }, "explanations" : [ { "index" : "gb", "valid" : false, "error" : "org.elasticsearch.index.query.QueryParsingException: [gb] No query registered for [tweet]" } ] }
理解查詢語句
對於合法查詢,使用 explain
引數將返回可讀的描述,這對準確理解 Elasticsearch 是如何解析你的 query 是非常有用的:
GET /_validate/query?explain { "query": { "match" : { "tweet" : "really powerful" } } }
我們查詢的每一個 index 都會返回對應的 explanation
,因為每一個 index 都有自己的對映和分析器:
{ "valid" : true, "_shards" : { ... }, "explanations" : [ { "index" : "us", "valid" : true, "explanation" : "tweet:really tweet:powerful" }, { "index" : "gb", "valid" : true, "explanation" : "tweet:realli tweet:power" } ] }
從 explanation
中可以看出,匹配 really powerful
的 match
查詢被重寫為兩個針對 tweet
欄位的 single-term 查詢,一個single-term查詢對應查詢字串分出來的一個term。
當然,對於索引 us
,這兩個 term 分別是 really
和 powerful
,而對於索引 gb
,term 則分別是 realli
和 power
。之所以出現這個情況,是由於我們將索引 gb
中 tweet
欄位的分析器修改為 english
分析器。