ElasticSearch學習筆記十二(複合查詢)
技術標籤:ElasticSearchelasticsearch
本學習筆記基於ElasticSearch 7.10版本,舊版本已經廢棄的查詢功能暫時不做筆記,以後有涉及到再做補充。
參考官方文件:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/joining-queries.html
關係型資料庫中有表的關聯關係,在 ElasticSearch 中,也會有類似的需求,例如訂單表和商品表,我們可以使用以下兩種方式實現:
- 巢狀文件(nested)
- 父子文件
1、巢狀文件
巢狀文件 nested
在之前介紹欄位型別的筆記中有學習過:ElasticSearch學習筆記六(欄位型別 Field data types)
現在,假設有一個電影文件,每個電影都有演員資訊:
# 需要將 actors 定義為 nested 型別,否則欄位中的關聯關係會丟失
PUT movies
{
"mappings": {
"properties": {
"actors": {
"type": "nested"
}
}
}
}
PUT movies/_doc/1
{
"name": "霸王別姬",
"actors" : [
{
"name": "張國榮",
"gender": "男"
},
{
"name": "鞏俐",
"gender": "女"
}
]
}
巢狀型別的缺點:
首先我們來看一下 movies 索引中的文件數量
GET _cat/indices?v
我們只插入了一篇文件,但是文件數量卻變成3:
這是因為 nested
文件在 ElasticSearch 內部其實也是獨立的 lucene 文件,也就是說actors 在內部單獨儲存為兩份文件。只是在我們查詢的時候,ElasticSearch 內部幫我們做了 join 處理,所以最終看起來就像一個獨立文件一樣。
如果 nested
文件中的資料比較多時,可能會生成多分巢狀文件,所以這種方案效能並不是特別好。
此外,nested
文件更新的時候,也會更新所有的巢狀文件,比較耗效能。
2、巢狀查詢
使用巢狀查詢 nested
來查詢巢狀文件:
GET movies/_search
{
"query": {
"nested": {
"path": "actors",
"query": {
"bool": {
"must": [
{
"match": {
"actors.name": "張國榮"
}
},
{
"match": {
"actors.gender": "男"
}
}
]
}
}
}
}
}
3、父子文件
nested
文件中,巢狀的文件只能是一對一的關係,不能複用。比如“張國榮”還演過其他電影,那隻能在每一部電影的 actors 中再新增一次。
而父子文件就可以解決這種問題,相比於巢狀文件,主要有如下優勢:
- 更新父文件時,不會重新索引子文件。
- 建立、修改或者刪除子文件時,不會影響父文件或其他子文件。
- 子文件可以作為搜尋結果獨立返回。
例如學生和班級的關係:
PUT stu_class
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"s_c": {
"type": "join",
"relations": {
"class": "student"
}
}
}
}
}
解釋一下幾個引數的意義:
s_c
:表示定義父子文件的欄位名,可以自定義。join
:表示這是一個父子文件。relations
:裡面分別定義 class 是 parent 父文件,student 是 child 子文件。
插入兩個父文件:
PUT stu_class/_doc/1
{
"name": "一班",
"s_c": {
"name": "class"
}
}
PUT stu_class/_doc/2
{
"name": "二班",
"s_c": {
"name": "class"
}
}
插入三個子文件:
PUT stu_class/_doc/3?routing=1
{
"name": "zhangsan",
"s_c": {
"name": "student",
"parent": 1
}
}
PUT stu_class/_doc/4?routing=1
{
"name": "lisi",
"s_c": {
"name": "student",
"parent": 1
}
}
PUT stu_class/_doc/5?routing=2
{
"name": "wangwu",
"s_c": {
"name": "student",
"parent": 2
}
}
注意: 子文件需要和父文件在同一個分片上,而 ElasticSearch 預設使用文件 _id 進行雜湊計算後決定存在哪個分片上,所以 routing 關鍵字的值為父文件的 id 即可。
另外,一個索引只能定義一個 join 欄位,可以向一個已經存在的 join 欄位上新增關係。
3.1、has_child
通過子文件查詢父文件使用 has_child
查詢:
GET stu_class/_search
{
"query": {
"has_child": {
"type": "student",
"query": {
"match": {
"name": "lisi"
}
}
}
}
}
3.2、has_parent
通過父文件查詢子文件使用 has_parent
查詢:
GET stu_class/_search
{
"query": {
"has_parent": {
"parent_type": "class",
"query": {
"match": {
"name": "一班"
}
}
}
}
}
注意: 使用 has_parent
查詢不進行評分,這時可以考慮使用 parent_id
查詢子文件:
GET stu_class/_search
{
"query": {
"parent_id": {
"type": "student",
"id": 1
}
}
}
4、小結
- 普通子物件實現一對多(不使用
nested
或父子文件),會損失子文件的邊界,子物件之間的關聯關係丟失。 nested
可以解決關聯關係丟失的問題,但是有兩個缺點:更新主文件的時候要全部更新,不支援子文件屬於多個主文件。- 父子文件則可以解決
nested
的問題,但是主要適用於寫多讀少的場景。
版權宣告:
本文僅記錄ElasticSearch學習心得,如有侵權請聯絡刪除。
更多內容請訪問原創作者:江南一點雨