es中如何使用巢狀物件查詢

仙人掌發表於2021-02-08

最近公司的APP使用ES搜尋功能時遇到一個需求——需要搜尋出來的資料中只包含某個商戶下的商品,且這些商品的庫存都不為0。

首先我們搜尋得到的文件格式簡化後如下:
file
也就是說,此時,我的需求的搜尋條件是:merchant_id=11,且stock不為0。如果按照需求來說,上面截圖裡的這個商品是不符合條件的,也就是不應該被搜尋出來。但是如果按照es一般的filter寫法去寫,filter部分的寫法應該是如下——

"filter":[
    { "terms":
            {"sku_list.merchant_id":[11]}
    },
    {"range":
            {"sku_list.stock":{"gte":1}}
    }
]

但這種寫法會導致那個商品依然會被搜出來,原因是elasticsearch(lucene)使用的庫沒有內部物件的概念,因此內部物件被扁平化為一個簡單的欄位名稱和值列表。也就是說,上面的商品文件,在es中會被轉換為

{
"id": [ **** ],
"name": [ ****],
"title": [ **** ],
"post": [ **** ] ,
"sku_list.sku_id": [ 100, 101, 102],
"sku_list.stock": [ 2,0,3 ],
"sku_list.merchant_id": [ 10, 11, 12 ],
"sku_list.sale_price": [ 128.00 ]
}

所以sku_list裡的stock跟merchant_id不再具有關聯關係,因為整合後的物件滿足了上面兩個條件,所以可以被搜尋出來。
要解決這個問題,我們需要對es的對映(mapping)進行一些小改動,將sku_list的type改為nested(巢狀資料型別,引用其他地方的一個說法:在內部,巢狀物件將陣列中的每個物件索引為單獨的隱藏文件,這意味著可以獨立於其他物件查詢每個巢狀物件)。nested型別是物件資料型別的專用版本,它允許物件陣列以可以彼此獨立查詢的方式進行索引。
file
那麼如何將sku_list改為nested型別,又儘可能不影響線上已有的功能呢?
首先,可以檢視當前索引下的所有對映以及對映型別。

curl -X GET "http://domain/my_index/_mapping"(domain是es所在伺服器ip+埠,my_index換成對應的索引名稱)

檢視後發現sku_list對應的索引型別是text。但是es不允許直接修改或刪除一個欄位型別,所以通用的修改欄位型別的解決辦法是——

採用reindex的方法實現,就是建立一個新的mapping,裡面的欄位型別按照新的型別定義,然後使用reindex的方法把原來的資料拷貝到新的index下面

1、建立新的索引my_index_new

curl -X PUT "http://domain/my_index_new?pretty"

2、將索引的預設欄位數調大(預設是1000,一般不需要調,但因為我們的文件比較複雜,欄位數使用超過了1000,所以必須調整)

curl -X PUT "http://domain/my_index_new/_settings" -d '{"index.mapping.total_fields.limit": "3000"}'

3、將sku_list的欄位型別設定為nested(注意這一步一定要再下一步同步資料前完成,不然同步完資料後,sku_list的欄位型別又會被設定成了預設的text,就又無法再更改了)

curl -X POST "http://domain/my_index_new/_mapping" -d '{"properties": {"sku_list": {"type":"nested"}}}'  

4、將老資料同步到新資料

curl -X POST "http://domain/_reindex" -d '{ "source": {    "index": "my_index"  },  "dest": {    "index": "my_index_new"  }} '

如果原先資料量較大,第三步花的時間會比較長,需要耐心等待其同步完成。

5、刪除原先索引(其實如果業務允許的話,可以直接在業務側將索引改為my_index_new,老的索引就不刪除,放著以防後面出現問題可以及時切換)

curl -X DELETE "http://domain/my_index"

6、設定原索引的別名(如果業務側不方便改es介面處的索引,那隻能通過4、5這種方法來確保原索引名正常使用,我們是直接用新的索引名稱,老的不去動它)

curl -X POST "http://domain/_aliases" -d '{ "actions": [{"add":{"index":"my_index_new","alias":"my_index"}}]} '

如果上述步驟都正常完成,此時再檢視索引的mapping,會發現sku_list已經是我們需要的nested型別了。

此時就可以使用nested的語法結構來構造查詢語句。

{"query":{"bool":{"must":[{"dis_max":{"queries":[]}},{"nested":{"path":"sku_list","query":{"bool":{"must":[{"terms":{"sku_list.merchant_id":[11]}},{"range":{"sku_list.stock":{"gte":1}}}]}}}}],"filter":[]}}}

只展示了nested部分的結構,其他的可以根據實際情況替換。其中,nested裡的path就是我們修改了型別的欄位,另外就是注意nested在filter裡使用貌似會報錯,所以需要寫在must查詢條件裡。

另外可以通過瀏覽器直接訪問domain/_cat/indices?v,看到新的索引的資訊,確認新索引的文件記憶體大小是否跟老的一致。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章