Elasticsearch之查詢與過濾。
查詢與過濾
事實上關於結構化查詢語句,我們可以使用兩種結構化語句:結構化查詢(Query DSL)和結構化過濾(Filter DSL)。查詢與過濾語句非常相似,但是它們由於使用目的不同而稍有差異。
一條過濾語句會詢問每個文件的欄位值是否包含著特定值:
- 是否 created 的日期範圍在 2013 到 2014 ?
- 是否 status 欄位中包含單詞“published” ?
- 是否 lat)lon 欄位中的地理位置與目標點相距不超過 10km?
一條查詢語句與過濾語句相似,但問法不同:
查詢語句會詢問每個文件的欄位值與特定值的匹配程度如何?
查詢語句的典型用法是為了找到文件:
- 查詢與 full text search 這個詞語最佳匹配的文件。
- 查詢包含單詞 run,但是也包含 runs、running、jog或sprint的文件。
- 同時包含著quick、brown和fox——單詞間離得越近,該文件的相關性越高。
- 標識著lucene、search或java——標識詞越多,該文件的相關性越高。
一條查詢語句會計算每個文件與查詢語句的相關性,會給出一個相關性評分 _source,並且按照相關性對匹配到的文件進行排序。這種評分方式非常適用於一個沒有完全配置結果的全文字搜尋。
效能差異
使用過濾語句得到的結果集——一個簡單的文件列表,快速匹配運算並存入記憶體是十分方便的,每個文件僅需要1個位元組。這些快取的過濾結果集與後續請求的結合使用是非常高效的。
查詢語句不僅要找出相匹配的文件,還需要計算每個文件的相關性,所以一般來說查詢語句要比過濾語句更耗時,並且查詢結果也不可快取。
幸虧有了倒排索引,一個只匹配少量文件的簡單查詢語句在百萬級文件中的查詢效率會與一條經過快取的過濾語句旗鼓相當,甚至略佔上風。但是一般情況下,一條經過快取的過濾查詢要遠勝一條查詢語句的執行效率。
過濾語句的目的就是縮小匹配的文件結果集,所以需要仔細檢查過濾條件。
什麼情況下使用
原則上來說,使用查詢語句做全文字搜尋或其他需要進行相關性評分的時候,剩下的全部用過濾語句。
最重要的查詢過濾語句
Elasticsearch提供了豐富的查詢過濾語句,而有一些是我們較常用到的。現在我們快速的介紹一下這些最常用到的查詢過濾語句。
term過濾
term主要用於精確匹配哪些值,比如數字,日期布林值或not_analyzed的字串(未經分析的文字資料型別):
{ "term" : { "age" : 26 } }
{ "term" : { "date" : "2014-09-01" } }
{ "term" : { "public" : true } }
{ "term" : { "tag" : "full_text" } }
terms過濾
terms跟term有點類似,但terms允許指定多個匹配條件。如果某個欄位指定了多個值,那麼文件需要一起去做匹配:
{
"terms" : {
"tag" : { "search" , "full_text" , "nosql" }
}
}
range過濾
range過濾允許我們按照指定範圍查詢一批資料:
{
"range" : {
"age" : {
"gte" : 20,
"lt" :30
}
}
}
範圍操作符包含:
gt → 大於 gte → 大於等於
lt → 小於 lte → 小於等於
exists和missing過濾
exists和missing過濾可以用於查詢文件中是否包含指定欄位或沒有某個欄位,類似於SQL語句中的IS_NULL條件
{
"exists" : {
"field" : "title"
}
}
這兩個過濾只是針對已經查詢一批資料來,但是想區分出某個欄位是否存在的時候使用。
bool過濾
bool過濾可以用來合併多個過濾條件查詢結果布林邏輯,它包括一個操作符:
must 多個查詢條件的完全匹配,相當於and。
must_not 多個查詢條件的相反匹配,相當於not。
should 至少有一個查詢條件匹配,相當於or。
這些引數可以分別繼承一個過濾條件或者一個過濾條件的陣列:
{
"bool" : {
"must" : { "term" : { "folder" : "inbox" } } ,
"must_not" : { "term" : { "folder" : "index" } },
"should" : [
{ "term" : { "term" : { "starred" : true } } },
{ "term" : { "term" : { "unread" : true } } }
]
}
}
match_all查詢
使用match_all可以查詢到所有文件,是沒有查詢條件下的預設語句。
{
"match_all" : {}
}
此查詢常用於合併過濾條件。比如說你需要檢索所有的郵箱,所有的文件相關性都是相同的,所以得到的_source為1
match查詢
match查詢是一個標準查詢,不管你需要全文字查詢還是精確查詢基本上都要用到它。
如果你使用match查詢一個全文字欄位,它會在真正查詢之前用分析器先分析match一下查詢字元:
{
"match" : {
"tweet" : "About Search"
}
}
如果你使用match查詢一個全文字欄位,它會在真正查詢之前用分析器先分析match一下查詢字元:
{
"match" : {
"tweet" : "About Search"
}
}
如果用match下制定了一個確切值,在遇到數字、日期、布林值或者not_analyzed的字串時,它將為你搜索你給定的值:
{ "match" : { "age" : 26 } }
{ "match" : { "date" : "2014-09-01" } }
{ "match" : { "public" : true } }
{ "match" : { "tag" : "full_text" } }
提示:做精確匹配搜尋時,你最好用過濾語句,因為過濾語句可以快取資料。
不像我們以前介紹的字元查詢,match查詢不可以用類似“+usid:2+tweet:search”這樣的語句。它只能就指定某個確切欄位某個確切的值進行搜尋,而你要做的就是為它指定正確的欄位名以避免語法錯誤。
multi_match查詢
multi_match查詢允許你做match查詢的基礎上同時搜尋多個欄位:
{
"multi_match" : {
"query" : "full text search",
"fields" : [ "title" , "body" ]
}
}
bool查詢
bool查詢與bool過濾相似,用於合併多個查詢子句。不同的是,bool過濾可以直接給出是否匹配成功,而bool查詢要計算每一個查詢子句的_source(相關性分值)。
must:查詢指定文件一定要被包含。
must_not:查詢指定文件一定不要被包含。
should:查詢指定文件,有則可以為文件相關性加分。
以下查詢將會找到title欄位中包含“how to make millions”,並且“tag”欄位沒有被標識為span。如果有標識為“starred”或者釋出日期為2014年之前,那麼這些匹配的文件將比同類網站等級高:
{
"bool" : {
"must" : { "match" : { "title" : "how to make milliions" } },
"must_not" : { "match" : {"tag" : "span"} },
"should" : {
{ "match" : { "tag" : "starred" } },
{ "range" : { "date" : { "gte" : "2014-01-01" } } }
}
}
}
提示:如果bool查詢下沒有must子句,那至少應該有一個should子句。但是如果有must子句,那麼沒有should子句也可以進行查詢。
查詢與過濾條件的合併
查詢語句和過濾語句可以放在各自的上下文中。在Elasticsearch API中我們會看到許多帶有query或filter的語句。這些語句既可以包含單條query語句,也可以包含一條filter子句。換句話說,這些語句需要首先建立一個query或filter的上下文關係。
複合查詢語句可以加入其它查詢子句,複合過濾子句也可以加入其它過濾子句。通常情況下,一條查詢語句需要過濾語句的輔助,全文字搜尋除外。
所以說,查詢語句可以包含過濾子句,反之亦然,以便於我們切換query或filter的上下文。這就要求我們在讀懂需求的同事構造正確有效的語句。
帶過濾的查詢語句
過濾一條查詢語句
比如說我們有這樣一條查詢語句:
{
"match" : {
"email" : "business opportunity"
}
}
然後我們想要讓這條語句加入term過濾,在收信箱中匹配郵件:
{
"term" : {
"folder" : "inbox"
}
}
search API 中只能包含query語句,所以我們需要用filtered來同時包含“query”和“filter”子句:
{
"filtered" : {
"query" : { "match" : { "email" : "business opportunity" } },
"filter" : { "term" : { "folder" : "inbox" } }
}
}
單條過濾語句
在query上下文中,如果你需要一條過濾語句,比如在匹配全部郵件的時候,你可以省略query子句:
GET /_search
{
"query" : {
"filtered" : {
"filter" : { "term" : { "folder" : "inbox" } }
}
}
}
如果一條查詢語句沒有指定查詢範圍,那麼它預設使用match_all查詢,所以上面語句的完整形式如下:
GET /_search
{
"query" : {
"filtered" : {
"query" : { "match_all" : {} },
"filter" : { "term" : { "folder" : "inbox" } }
}
}
}
查詢語句中的過濾
有時候,你需要在filter的上下文中使用一個query子句。下面的語句就是一條帶有查詢功能的過濾語句,這條語句可以過濾掉看起來像垃圾郵件的文件:
GET /_search {
"query" : {
"filtered" : {
"bool" : {
"must" : { "term" : { "folder" : "inbox" } },
"must_not" : {
"query" : { <1>
"match" : { "email" : "urgent business proposal" }
}
}
}
}
}
}
<1> 過濾語句中可以使用query查詢的方式代替bool過濾子句。
提示:我們很少用到的過濾語句中包含查詢,保留這種用法只是為了語法的完整性。只有在過濾中用到全文字匹配的時候才會使用這種結構。
驗證查詢
查詢語句可以變得非常複雜,特別是與不同的分析器和欄位對映相結合後,就會有些難度。
validate API可以驗證一條查詢語句是否合法。
GET /gb/tweet/_validate/query
{
"query" : {
"tweet" : {
"match" : "really powerful"
}
}
}
以上請求的返回值告訴我們這條語句是非法的:
理解錯誤資訊
想知道語句非法的具體錯誤資訊,需要加上explain引數:
GET /gb/tweet/_validate/query?explain <1>
{
"query" : {
"tweet" : {
"match" : "really powerful"
}
}
}
<1> explain 引數可以提供語句錯誤的更多詳情。
很顯然,我們把query語句的match與欄位名位置弄反了:
理解查詢語句
如果是合法語句的話,使用explain引數可以返回一個帶有查詢語句的可閱讀描述,可以幫助瞭解查詢語句在ES中是如何執行的:
GET /_validate/query?explain
{
"query" : {
"match" : {
"tweet" : "really powerful"
}
}
}
explanation會為每一個索引返回一段描述,因為每個索引會有不同的對映關係和分析器:
從返回的explanation你會看到match是如何為查詢字串“really powerful”進行查詢的,首先,它被拆分成兩個獨立的詞分別在tweet欄位中進行查詢。
而且,在索引us中這兩個詞為“really” 和 “powerful”,在索引gb中被拆分成“really”和“power”。這是因為我們在索引gb中使用了english分析器。