1. 程式人生 > 其它 >ElasticSearch學習筆記十二(複合查詢)

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、小結

  1. 普通子物件實現一對多(不使用 nested 或父子文件),會損失子文件的邊界,子物件之間的關聯關係丟失。
  2. nested 可以解決關聯關係丟失的問題,但是有兩個缺點:更新主文件的時候要全部更新,不支援子文件屬於多個主文件。
  3. 父子文件則可以解決 nested 的問題,但是主要適用於寫多讀少的場景。

版權宣告:

本文僅記錄ElasticSearch學習心得,如有侵權請聯絡刪除。
更多內容請訪問原創作者:江南一點雨
微信公眾號:江南一點雨