ElasticSearch(七)--請求體查詢
簡單查詢lite search (字串查詢)是一種有效的命令列ad hoc 查詢,但是想要善用搜索,必須使用請求體查詢request body search API.之所以這麼稱呼,是因為大多數的引數以JSON格式所容納,而不是查詢字串.
請求體查詢不但可以處理查詢,而且還可以高亮返回結果中的片段.
1.空查詢
GET _search
{}
同字串查詢一樣,你可以查詢一個,或多個索引及型別
GET /index_2014*/type1,type2/_search
{}
也可以使用from, size引數進行分頁pagination:
注意,from和size數值均可以與實際不符,返回的不過是個空的陣列,並不會出錯.GET /website/_search { "from":1, "size":3 }
那麼,這中請求體查詢,使用的是攜帶內容的GET請求方式?
任何一種語言(特別是js)的HTTP庫都不允許GET請求中攜帶互動資料,使用者會很驚訝GET請求會允許攜帶互動資料.
但是真實情況是,一份關於HTTP協議的標準文件RFC中並未定義一個GET請求攜帶請求體會發生什麼!所以,
ES的作者們傾向於使用GET提交查詢請求,因為它們覺得這個詞相比於POST能更好的描述這種行為.然而,因為攜帶請求體的GET請求並不被廣泛支援,所以search API同樣支援POST請求.
相比於神祕的字串查詢方式,請求體查詢允許我們通過使用query DSL(Domian Specific Language)來寫入引數.POST /website/_search { "from":1, "size":3 }
2. Query DSL
query DSL是一種靈活的,表現力強的查詢語言,ES通過一個簡單的JSON介面使用DSL來表現lucene絕大多數的能力.
應當在你的產品中使用這種方式進行查詢,它是你的查詢更加靈活,精準,易於閱讀,且易於debug.
為了使用query DSL,傳遞一個查詢給query引數:
GET /_search
{
"query": YOUR_QUERY_HERE
}
例如,空查詢,其實就相當於使用了一個match_all查詢子句
match_all是一個查詢子句,正如其名字一樣,查詢所有文件.POST /website/_search { "query": { "match_all": {} } }
查詢子句的結構
一個查詢子句的典型結構:
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
如果它是與特定欄位有關的:
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}
例如,你可以使用match查詢子句,查詢在欄位tweet中有elasticsearch的:
查詢自己的格式:
{
"match": {
"tweet": "elasticsearch"
}
}
請求:
GET /_search
{
"query": {
"match": {
"tweet": "elasticsearch"
}
}
}
稱之為查詢子句query clause,代表其都是放到query語句下的.
合併多子句
查詢子句像一個簡單的積木塊一樣,可以和其他的子句組合,構成複雜的查詢.
子句可以分為:
葉子子句leaf clause,被用作字串與欄位的比較.
複合子句compound clause,備用做合併其他的子句.例如一個bool子句,允許合併其他的子句:must 匹配, must_not,should.它還允許包含non-scoring, filters作為結構化搜尋:
{
"bool": {
"must": { "match": { "tweet": "elasticsearch" }},
"must_not": { "match": { "name": "mary" }},
"should": { "match": { "tweet": "full text" }},
"filter": { "range": { "age" : { "gt" : 30 }} }
}
}
非常重要的指出,一個複合查詢子句可以包含其他任何查詢子句,或者別的複合子句.這意味著複合子句可以被相互巢狀,允許複雜的邏輯表達.
例如,下邊的例子,查詢郵件,滿足:包含business opportunity,同時被標星的郵件;或者同時在folder,indbox,但是沒有被標記為spam的郵件.
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "match": { "folder": "inbox" }},
"must_not": { "match": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}
不要擔心這些例子的細節,我們後續會解釋.重點是明白複合語句可以組合多個子句,包括葉子子句或這複合子句到一個簡單的查詢中.
3. 查詢和過濾
ES使用DSL將查詢子句放到一個簡單的集裡,這種簡單集合可以被用作兩種環境:過濾上下文Filtering context和查詢上下文query context.
當被用到過濾環境中,查詢query被稱作non-scoring or filtering query,這樣的查詢會這樣問問題,'這個文件是否匹配?'答案是二選一,是或否.
例如;
created 的日期範圍是否介於2013-2014?
status欄位是否包含詞published?
las_lon欄位的地理位置是否與目標相距不超過10km?
當被使用在查詢環境中,查詢成為scoring query,它這樣問"這個文件的匹配程度如何?"
查詢典型的使用:
查詢與full text search 最佳匹配的文件
包含單詞run,也可能是running,runs,jog, sprint
同時包含quick , brown, fox,它們離得越近,文件的匹配相關性越高.
標記著lucene, search, java,標識詞越多,文件的相關性越高.
一個scoring query,計算文件與查詢的相關性,並賦值給欄位_score,用作依據相關性排序的標準.這種概念同樣適用於全文搜尋.
注意:
歷史上,在ES中,查詢和過濾是分開做的,在ES2.0開始,過濾被技術性的消除,同時,查詢開始支援non-scoring式的查詢.
然而為了區分和簡便,我們仍用"過濾"一詞來描述non-socring的查詢.你可以把filter , filter query , non-scoring query當作一樣的.
同樣的,如果查詢一詞被單獨的使用,我們就認為是scoring的查詢.
效能差異
過濾查詢是一個簡單的包含與不包含的檢查,這是它們計算非常快速.
有各種優化,對於至少有一條過濾查詢是很少有文件匹配,同時被頻繁的用作non-scoring的查詢,可以被放到記憶體中,更快速獲取.
相比之下,scoring查詢不但需要查詢匹配的文件,並且還要計算相關性,這使得其繁重於non-scoring查詢,同時查詢的結果是不能夠被快取的.
幸虧有倒排索引,使得一個簡單的scoring查詢,僅匹配一些文件,效能可以與過濾相比,甚至優於過濾,在跨越數以百萬計的檔案中.
但是一般情況下,過濾是優於查詢的.
過濾的目的是減少文件的數量,這些文件必須被scoring query檢查.
什麼時候使用?
一般原則,在全文查詢,或者需要相關性評分時,使用查詢scoring query,其他時候都是使用過濾non-scoring query.
4. 重要的查詢語句
ES有很多查詢語句,只有少部分經常被使用,我們會在後續的深入查詢一章詳細學習,現在快速介紹一些重要的語句.
match_all
match_all查詢簡單的匹配所有文件
{ "match_all": {}}
這個查詢經常和過濾器一起使用.
match
match查詢是一個標準的查詢,無論是查詢一個全文文字還是精確值.
如果使用match對全文文字欄位進行查詢,執行查詢之前,先使用針對該欄位正確的分析器對查詢字串進行分析.
{ "match": { "tweet": "About Search" }}
如果使用該語句在一個欄位上匹配精確值,數值,日期,布林,以及not_analyzed字串,
{ "match": { "age": 26 }}
{ "match": { "date": "2014-09-01" }}
{ "match": { "public": true }}
{ "match": { "tag": "full_text" }}
對於確切值搜尋,你可能想使用過濾語句,而不是查詢,我們很快看到過濾的例子.
相比於字串查詢,match語句查詢的語法更加安全.
multi_match
multi_match查詢允許在多個欄位上進行match一樣的查詢
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
rangerange查詢允許查詢數值或日期在一個指定的區間裡,該子句接受如下引數:
gt : greater than
gte : greater than or equal to
lt : less than
lte : less than or equal to
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
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" ] }}
exist, missingexist, missing查詢被用作查詢指定的欄位存在的文件(exist)或者不存在的文件(missing),exist返回存在該欄位的文件,missing返回不存在該欄位的文件
{
"exists": {
"field": "title"
}
}
5. 組合查詢
現實應用中的查詢從來都不是簡單的,使用多個輸入值查詢多個欄位,依據一系列標準的過濾器.構造一個複雜查詢,你需要一種組合多個查詢子句在一個搜尋請求中的方式.
為了達到這個要求,可以使用bool查詢,這個查詢接受如下引數:
must: 必須是匹配的文件被包含進來
must_not: 一定是不匹配的文件被包含進來
should: 如果匹配,增加_score,否則沒有影響,為每個文件相關性評分.
filter: 必須匹配,是non_scoring的過濾模式,只是簡單的包含或不包含.
因為這是我們看到的第一個包含其他查詢的查詢語句,我們需要談論相關性評分是怎麼計算的.
每個子句分別計算文件的相關性評分,一旦這些結果被計算出來,bool語句將這些分數合併到一起,並且返回一個單個分數值,代表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" }}}
]
}
}
加上過濾查詢:
如果我們不想文件的日期對評分產生影響,我們可以使用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查詢放入filter子句,我們轉化它為non-scoring查詢,它不再對文件的相關性評分產生影響,並且因為是non-scoring查詢,可以使用過濾器的優化來提升效能.
任何一個查詢都可以使用這種方式,簡單的將查詢放到bool語句的filter子句中,會自動轉化為non-scoring過濾.
如果需要一個基於多標準的過濾,bool查詢本身可以作為non-scoring查詢
{
"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" }}
]
}
}
}
}
constant_score查詢
儘管不如bool查詢經常使用,constant_score查詢也依然是有用的,該查詢為匹配的文件應用靜態的,常數的分數.它主要是在執行過濾查詢時使用.
只有過濾子句時,你可以使用該語句代替bool語句.效能是相同的,但是有利於查詢的簡單性和清晰度
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}
6. 驗證查詢
查詢可以是非常複雜,尤其是組合了不同的分析器和欄位對映的時候,validate-query API可以檢查一個請求是否有效.
在請求URL後加/_validate/query
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"
}
}
}
顯然,我們混淆了查詢語句的類別和欄位的名字
{
"valid" : false,
"_shards" : { ... },
"explanations" : [ {
"index" : "gb",
"valid" : false,
"error" : "org.elasticsearch.index.query.QueryParsingException:
[gb] No query registered for [tweet]"
} ]
}
我們也可以利於expalin引數理解ES是如何解釋查詢的:
GET /us,gb/_validate/query?explain
{
"query": {
"match": {
"tweet": "really powerful"
}
}
}
為我們查詢的每個索引返回一個explanation,因為每個索引有不同的對映和分析器:
{
"valid": true,
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"explanations": [
{
"index": "gb",
"valid": true,
"explanation": "tweet:realli tweet:power"
},
{
"index": "us",
"valid": true,
"explanation": "tweet:really tweet:powerful"
}
]
}
從explanation中,我們可以看出針對tweet欄位,match語句是如何將查詢字串really powerful 重寫為兩個單個詞term的.
兩個索引的重寫詞不一樣,原因是因為索引gb中的tweet欄位使用的是english分析器.