廚老伯開槍中

elasticsearch 查詢設計記錄

elastic search json 參數

最近在處理與後端 elastic search 介接的功能

目標是於 2-3個欄位中搜尋使用者所輸入的關鍵字,然後回傳對應的產品編號
關鍵字中有空白的話,視為不同詞組 (就是說用空白分組)

幾個學習網站列表如下

  1. Elasticsearch 权威指南(Elasticsearch the definitive guide 中文版 1)
  2. Elasticsearch 权威指南 Elasticsearch the definitive guide 中文版 2
  3. 不能少的官方網站,不過沒幫上太多忙,畢竟我只需要寫出這個功能...

簡單的事後心得

  • 這東西好怪阿.. query/filter 的界線到底在哪?
  • 這東西好怪阿.. aggs 應該要吃過濾出的資料,怎麼會因為query/filter 端沒給資料就跑去撈整個 pool 呢..
  • 這東西好怪阿.. 為什麼要用 multi_match 才能處理兩個以上中文詞組?

以下開始記錄處理過程中的心得
elastic search(以下簡稱 ES)已知有兩種搜尋格式
一個是 q=terms:sony
另一個是 json 格式

{
  "query": {
    "term": {
      "item_name": "sony"
    }
  }
}

Elastic Query DSL 說明

大致說明如下
QDSL 提供了基本查詢,如 term 和 prefix
可以使用 bool 查詢進行複合查詢 (例如 where a=xx and b=yyy )
query 可以與 filtered 或 constant_score 之類查詢連結以處理過濾查詢
QDSL 中,某些查詢可以與其他查詢連結,例如 bool query,而某些可以與其他 filter 連結,例如 constant_score,而另外一些則可以同時連結 query 與 filter,例如 filtered query
以上提及的都可以包含任意數量的 query 和 filter (只是結構會變得很複雜...),因此可以讓事情變得更複雜美妙和buggy有趣
Filter 在使用上很便利,因為 filter 會自動快取,而且 filter 還提供了重要性順序,而一般查詢並不會給予搜尋結果重要順序

以下是兩段不仔細看會以為是精神分裂的說明 orz

全文檢索或需要相關性分數時,應該要用 query
As a general rule, queries should be used instead of filters:

  • for full text search
  • where the result depends on a relevance score

二元搜尋或執行精確比對時,應該要用 filter
As a general rule, filters should be used instead of queries:

  • for binary yes/no searches
  • for queries on exact values

基本說明大概就這樣...
以上那幾段的意思,就是說搜尋資料物件要根據目的決定第一層是 query 或 filter, 然後有多個條件的話,就用 bool 連接
在 query/fitler 中又想要用 filter/query 的話,就要用 fitlered 連接
而最底層的條件則是用 term/prefix 處理
所以一個要用來查詢名稱欄位有沒有 sony 或 htc 的格式應該是

{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "item_name": "htc"
          }
        },
        {
          "term": {
            "item_name": "sony"
          }
        }
      ]
    }
  },
  "size": 100
}

上面 (第一層) 的 size 100 是用於指定要顯示幾筆 hit (搜尋結果) 資料
should 則是 bool 中 or 的意思, 其他同級的有 must, must_not
should 陣列中的數量沒有限制
也就是說,我們要在兩個欄位搜尋兩組字詞的話,只要繼續擴增就好

到這邊查詢條件組合就大致完成了.....嗎?
太甜了,承認這是你太年輕時犯下的錯誤吧

如果你有已經架設好的 elastic search
你可以嘗試輸入以下兩個查詢資料看看有沒有結果
內容請修改成符合自己的資料欄位與關鍵字

{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "item_name": "手"
          }
        },
        {
          "term": {
            "item_name": "平"
          }
        },
        {
          "term": {
            "description": "手"
          }
        },
        {
          "term": {
            "description": "平"
          }
        }
      ]
    }
  },
  "size": 100
}
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "item_name": "手機"
          }
        },
        {
          "term": {
            "item_name": "平板"
          }
        },
        {
          "term": {
            "description": "手機"
          }
        },
        {
          "term": {
            "description": "平板"
          }
        }
      ]
    }
  },
  "size": 100
}

以上兩者的差異在於中文字數是一個還是兩個....
這邊不確定是我對 term 的理解有誤或是踩到什麼地雷
總之就是當查詢關鍵字為中文且字數達兩個以上時,該條件不會發生效果

我測試出來的解法是使用 multi_match 如下

{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "手機",
            "type": "most_fields",
            "fields": [
              "description",
              "item_name"
            ]
          }
        },
        {
          "multi_match": {
            "query": "sony",
            "type": "most_fields",
            "fields": [
              "description",
              "item_name"
            ]
          }
        },
        {
          "multi_match": {
            "query": "平板",
            "type": "most_fields",
            "fields": [
              "description",
              "item_name"
            ]
          }
        }
      ]
    }
  }
}

multi_match 說明
然後這說明有夠爛的..
我實在看不出來 type 中的 "Finds documents which match any field" 中的 fields 到底是指定的 fields 或者是文件中的所有 fields
後者的話,我還要指定 fields 內容幹嘛?你都強制搜尋全部欄位了...
前者的話,..你為什麼要用這麼肯定確定一定根性+気合+加速+幸運+努力+必中+必閃+熱血的語氣!?

接下來就是收尾工作,取資料了
這部分不是一定要做的
我這邊是因為出來的結果中,商品編號欄位重複性太高 (前端每天開數次爬蟲,爬蟲會把搜尋結果與商品重新再次建立資料..那我要前幾天的資料幹嘛?)
所以必須要對結果過濾

這邊會用到 aggregations, 關鍵字是 aggs
大致長相如下

{
  "aggs": {
    "distinct_item_no": {
      "terms": {
        "field": "item_no",
        "size": 100
      }
    }
  }
}

其中
aggs 是關鍵字
distinct_item_no (似乎)沒有實際作用,只是一個方便用於自我辨識的欄位
這邊的 size 是用來指定 aggs 回傳結果 (通常稱為 buckets) 的大小

這邊最大的重點是確定buckets的資料來源..
如前所述,我處理的資料有點亂
最相關的同一筆資料會佔據最前面數百至數千名,所以非得用 aggregation 處理
但是也因為最前面數百至數千名都是同一筆資料,很難從前端進行驗證
所以我有一段時間都是在處理一個問題:為什麼輸入任何關鍵字得到的資料都一樣??
我本來以為是前面提及的 match any fields 這問題導致
不過後來發現是 aggs 資料源問題
目前不確定是因為查詢資料合法但無結果,導致 aggs 吃不到資料,因此 aggs 就自己跑去吃預設資料 (所有資料)
或者是查詢資料合法但條件設定錯誤,把所有資料都塞給了 aggs...
要驗證這問題就要去驗證 hits 結果,就是那個前面數百到數千都是同一商品的那個結果.. orz
嗯,總之,結論就是這個樣子 (放棄意味)

總之,這工作最後終於結束了..
不過我有預感這不會是真正的結束
希望這些資料下次可以派上用場

comments powered by Disqus