1. 程式人生 > >elasticsearch全域性檢索多分詞器匹配

elasticsearch全域性檢索多分詞器匹配

在es全域性檢索的需求中,需要進行多個分詞器同時匹配關鍵詞,例如:在商品名稱、品牌名稱和類目名稱中匹配含有“西”關鍵字的查詢結果,當一個欄位匹配時即加入查詢結果用sql語句表達為:select  *  from  item where item_name like '%西%' or brand_name like '%西%' or c_name like '%西%'

其中item_name,brand_name,c_name分別商品名稱、品牌名稱和類目名稱。

這個簡單的需求在es中卻實現比較困難,原因是es在索引資料時會針對欄位內容進行分詞,下面列出es幾種分詞器的特性

1)standard分詞器

      es預設的分詞器,對中文支援不友好,會將中文分成單字,這樣在查詢多個漢字時就匹配不到doc,所以針對中文欄位可使用ik。

2)ik分詞器

      需要單獨安裝ik外掛,有ik_smart和ik_max_word兩種分詞粒度,其中ik_max_word粒度更細,但如果ik識別不出的詞,就不會分出。

      導致上邊的全域性檢索例子查詢“西”時匹配不到資料。

3)pinyin分詞器

      需要安裝外掛,可支援拼音全拼、簡拼和首字母查詢。

鑑於以上分詞器的特性,在全域性檢索時可能需要使用幾種分詞器同時工作,那這種需求該如何來處理呢?答案是使用multi_field

以下為multi_field的mapping:

{
        "item" : {
            "properties" : {
                "item_name" : {
                    "type" : "
multi_field",
                    "fields" : {
                        "item_name_ik" : {"type" : "string", "analyzer" :"ik"},
                        "item_name_not" : {"type" : "string", "index" : "not_analyzed"},


                        "item_name_standard" : {"type" : "string"}
                    }
                },
               "brand_name" : {
                    "type" : "multi_field",
                    "fields" : {
                        "brand_name_ik" : {"type" : "string", "analyzer" :"ik"},
                        "brand_name_not" : {"type" : "string", "index" : "not_analyzed"},
                        "brand_name_standard" : {"type" : "string"}
                    }
                },
                "c_name" : {
                    "type" : "multi_field",
                    "fields" : {
                        "c_name_ik" : {"type" : "string", "analyzer" :"ik"},
                        "c_name_not" : {"type" : "string", "index" : "not_analyzed"},
                        "c_name_standard" : {"type" : "string"}
                    }
                }
            }
        }
    }

每個需要查詢的欄位分別設定不同的分詞器,查詢時的json如下:

{"from" : 0,
  "size" : 20,
  "query" : {
    "
bool" : {
      "
should" : [ {
       
"fuzzy" : {
          "item_name.item_name_ik" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "item_name.item_name_not" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "item_name.item_name_standard" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "brand_name.brand_name_ik" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "brand_name.brand_name_not" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "brand_name.brand_name_standard" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "c_name.c_name_ik" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "c_name.c_name_not" : {
            "value" : "西"
          }
        }
      }, {
        "fuzzy" : {
          "c_name.c_name_standard" : {
            "value" : "西"
          }
        }
      } ]
    }
  }
}

這樣就會針對所有分詞的情況,查詢到含有關鍵字“西”的文件,如果覺得這樣寫的結構比較麻煩,也可使用multi_match

如下:

{
  "
multi_match" : {
    "query" : "西",
    "fields" : [ "brand_name.brand_name_standard", "item_name.item_name_standard", "c_name.c_name_standard" ....]
  }
}

另外:

使用client客戶端api可根據欄位名獲取到mapping資訊,例如可根據item_name名字找到它下邊的c_name_standard等名稱

這樣在可簡化查詢條件的構建,程式碼如下:

//查詢item_name下的fileds設定,遍歷出各fields的名字放入list

List<String> list = new ArrayList<String>();
String fieldName = "item_name";
GetFieldMappingsRequest fieldMappingsRequest = new GetFieldMappingsRequest().indices(index).types(type).fields(fieldName);
GetFieldMappingsResponse responseActionFuture = client.admin().indices().getFieldMappings(fieldMappingsRequest).actionGet();
GetFieldMappingsResponse.FieldMappingMetaData fieldMappingMetaData = responseActionFuture.fieldMappings(index,type,fieldName);Object field = fieldMappingMetaData.sourceAsMap().get(fieldName);
if(field == null){
    return list;
}
Map<String, Object> fieldsMap = (Map)((Map)field).get("fields");
if(fieldsMap == null){
    return list;
}else{
    Iterator<Map.Entry<String, Object>> entries = fieldsMap.entrySet().iterator();
    while (entries.hasNext()) {
        Map.Entry<String, Object> entry = entries.next();
        System.out.println("Key = " + entry.getKey());
        list.add(entry.getKey());
    }
}
//構建查詢條件
SearchRequestBuilder builder = client.prepareSearch(index).setTypes(type);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
for(String field : list){
    boolQueryBuilder.should(QueryBuilders.fuzzyQuery(query.getKey() + "." +field, query.getValue()));
}
builder.setQuery(boolQueryBuilder);
SearchResponse searchResponse = builder.execute().actionGet();
SearchHits hits = searchResponse.getHits();