ElasticSearch常用結構化搜索
最近,需要用到ES的一些常用的結構化搜索命令,因此,看了一些官方的文檔,學習了一下。結構化查詢指的是查詢那些具有內在結構的數據,比如日期、時間、數字都是結構化的。
它們都有精確的格式,我們可以對這些數據進行邏輯操作,比較常見的操作包括比較時間區間,或者獲取兩個數字間的較大值。
精確查詢
當進行精確查詢時,過濾器filter是十分重要的,因為它們效率非常高,過濾器不計算相關性(直接跳過了整個記分階段)而且很容易進行緩存。
過濾數字
我們首先看 term filter,它最常用,可以用來處理數字,布爾值,日期和文本。
例如我們有一些產品:
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
我們想要做的是要查詢具有某個價格的所有產品,如果對於SQL熟悉,那麽它的表達式是:
SELECT * FROM products WHERE price = 20
在ES查詢中,我們使用 term 達到相同的目的:
{
"term" : {
"price" : 20
}
}
但是在ES裏,term 不能單獨使用,search API期望的是一個 query 而不是 filter,所以,我們需要把 term 放在一個filter query裏進行使用:
GET /my_store/products/_search
{
"query" : {
"filtered" : { #filtered 查詢同時接受一個 query 和 filter
"query" : {
"match_all" : {} #match_all 會返回所有匹配的文件,這是個默認行為
},
"filter" : {
"term" : { #term 過濾我們之前說到的,需要註意的是這裏 term塊 是處於 filter 之內的
"price" : 20
}
}
}
}
}
執行結果正如我們期望一樣,它只會返回文檔2,這裏我們稱為命中hit。
"hits" : [
{
"_index" : "my_store",
"_type" : "products",
"_id" : "2",
"_score" : 1.0, #1
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5"
}
}
]
之前我們說到filter不會進行記分或相關性計算,這裏的分數來自於我們查詢時使用的關鍵字 match_all
過濾文本
term 同樣可以用來過濾文本,如果我們想要查詢某個具體UPC id的產品,SQL語句會是下面這樣:
SELECT product FROM products WHERE productID = "XHDK-A-1293-#fJ3"
轉換成ES查詢,同樣使用 term 來查詢:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}
但這裏有個小問題,我們沒有如預期得到想要的結果!為什麽呢?問題並不出在 term 查詢上,問題出在數據索引的方式。如果使用 analyze API(Test Analyzers),我們可以看到這裏的UPC碼以及被拆分成多個小的token:
GET /my_store/_analyze?field=productID
XHDK-A-1293-#fJ3
結果
{
"tokens" : [ {
"token" : "xhdk",
"start_offset" : 0,
"end_offset" : 4,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "a",
"start_offset" : 5,
"end_offset" : 6,
"type" : "<ALPHANUM>",
"position" : 2
}, {
"token" : "1293",
"start_offset" : 7,
"end_offset" : 11,
"type" : "<NUM>",
"position" : 3
}, {
"token" : "fj3",
"start_offset" : 13,
"end_offset" : 16,
"type" : "<ALPHANUM>",
"position" : 4
} ]
}
所以,當我們用 term 去過濾值 XHDK-A-1293-#fJ3 的時候,找不到任何文件,因為這個token不在我們的反向索引(inverted index)之中,正如上面呈現的,索引裏面有4個token。
顯然,這種對於id碼或其他任何精確值的處理方式不是我們想要的。
為了避免這種問題,我們需要告訴ElasticSearch這個字段具有精確值,需要被設置成 not_analyzed 。 我們可以在定制化字段mapping中找到相關內容。為了修正這個問題,我們需要首先刪除老的index,然後再創建一個新的
DELETE /my_store #1
PUT /my_store #2
{
"mappings" : {
"products" : {
"properties" : {
"productID" : {
"type" : "string",
"index" : "not_analyzed" #3
}
}
}
}
}
然後我們就可以對文件重索引了:
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
組合過濾器
上面的兩個例子都是單個filter的使用方式,在實際中,我們很多情況下會同時會對多個值或字段使用filter。例如,在ElasticSearch中,如何標識下面這個SQL?
SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND (price != 30)
在這種情況下,我們需要 bool filter。這是一個復合過濾器可以接收多個參數,然後將他們組合成布爾組合。
布爾過濾器(Bool Filter)
bool filter包括三部分:
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
}
}
-
must:所有的語句必須匹配,與 AND 等價。
-
must_not:所有的語句都不能匹配,與 NOT 等價。
-
should:至少有一個語句匹配,與 OR 等價。
用ES查詢實現我們上面SQL裏的查詢:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}},
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}}
],
"must_not" : {
"term" : {"price" : 30}
}
}
}
}
}
}
我們搜索的結果返回了2個hits,兩個文件各滿足其中一個條件:
"hits" : [
{
"_id" : "1",
"_score" : 1.0,
"_source" : {
"price" : 10,
"productID" : "XHDK-A-1293-#fJ3"
}
},
{
"_id" : "2",
"_score" : 1.0,
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5"
}
}
]
嵌套布爾過濾器(Nesting Boolean Filters)
盡管 bool 是一個復合的過濾器,可以接受多個子過濾器,需要註意的是 bool 過濾器本身仍然是一個過濾器(filter)。這意味著我們可以將一個bool過濾器置於另外一個bool過濾器內部,這為我們提供了復雜布爾邏輯的處理能力:
對於一個SQL語句:
SELECT document FROM products WHERE productID = "KDKE-B-9947-#kL5" OR ( productID = "JODL-X-1937-#pV7" AND price = 30 )
我們將其轉換成一個嵌套的 bool 過濾器:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"bool" : {
"should" : [
{ "term" : {"productID" : "KDKE-B-9947-#kL5"}}, #1
{ "bool" : { #2
"must" : [
{ "term" : {"productID" : "JODL-X-1937-#pV7"}}, #3
{ "term" : {"price" : 30}} #4
]
}}
]
}
}
}
}
}
得到的結果有兩個文件,他們各滿足 should 中的一個條件:
"hits" : [
{
"_id" : "2",
"_score" : 1.0,
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5" #1
}
},
{
"_id" : "3",
"_score" : 1.0,
"_source" : {
"price" : 30, #2
"productID" : "JODL-X-1937-#pV7" #3
}
}
]
ElasticSearch常用結構化搜索