ES中使搜索词通过ik分词后匹配多个文档字段,根据字段权重和最大匹配原则进行返回的QueryBuilders创建

作者: admin 分类: ELK 发布时间: 2019-05-08 22:58  阅读: 40 views

最近在使用es制作搜索功能的时候,发现搜索结果不尽人意。使用默认分词器是将中文拆分成了单字,在查询时虽能返回包含相关字的结果,但是和整句的意思差别太大,无法直接根据一段文本精准查询。使用ik_analyze后,也只是好一点。应该是自己的用法不对,在本地不断尝试之后,终于成功。

整个搜索结构是差不多的。主要在于qb的构造

SearchSourceBuilder ssb = new SearchSourceBuilder()
.query(qb).from(0).size(60);

我这里的需求是在文档中搜索一个关键词key,分词后, 需要按照字段权重大小依次查询 columnA,columnB,columnC,columnD,columnE,columnF字段,进行最优返回。

写法一:

BoolQueryBuilder qb = QueryBuilders.boolQuery()
          .should(QueryBuilders.matchQuery("columnA",key).boost(6.0f))
          .should(QueryBuilders.matchQuery("columnB",key).boost(5.0f))
          .should(QueryBuilders.matchQuery("columnC",key).boost(4.0f))
          .should(QueryBuilders.matchQuery("columnD",key).boost(3.0f).analyzer("ik_smart").operator(Operator.AND))
          .should(QueryBuilders.matchQuery("columnE",key).boost(2.0f).analyzer("ik_smart"))
          .should(QueryBuilders.matchQuery("columnF",key).boost(1.0f));

这里是罗列了所有需要查询的列,并指定boost(类似权重,是提升文档搜索评分的重要因素)的大小,还根据不同的字段的数据类型指定不同的分词器analyzer,并指定分词的搜索方式operator

转换为查询语句后如下:

{
  "bool" : {
    "should" : [
      {
        "match" : {
          "columnA" : {
            "query" : "测试项目",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 6.0
          }
        }
      },
      {
        "match" : {
          "columnB" : {
            "query" : "测试项目",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 5.0
          }
        }
      },
      {
        "match" : {
          "columnC" : {
            "query" : "测试项目",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 4.0
          }
        }
      },
      {
        "match" : {
          "columnD" : {
            "query" : "测试项目",
            "operator" : "AND",
            "analyzer" : "ik_smart",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 3.0
          }
        }
      },
      {
        "match" : {
          "columnE" : {
            "query" : "测试项目",
            "operator" : "OR",
            "analyzer" : "ik_smart",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 2.0
          }
        }
      },
      {
        "match" : {
          "columnF" : {
            "query" : "测试项目",
            "operator" : "OR",
            "prefix_length" : 0,
            "max_expansions" : 50,
            "fuzzy_transpositions" : true,
            "lenient" : false,
            "zero_terms_query" : "NONE",
            "auto_generate_synonyms_phrase_query" : true,
            "boost" : 1.0
          }
        }
      }
    ],
    "adjust_pure_negative" : true,
    "boost" : 1.0
  }
}

以上格式稍显庞大,但是可以将规则制定的比较细致 ,是个反复尝试的过程。

写法二

Map<String,Float> fields = new HashMap<String,Float>(8);
      fields.put("columnA", 5.0f);
      fields.put("columnB", 4.0f);
      fields.put("columnC", 3.0f);
      fields.put("columnD", 2.0f);
      fields.put("columnE", 1.0f);
      fields.put("columnF", 6.0f);
MultiMatchQueryBuilder entireReg = QueryBuilders.multiMatchQuery(key, "username").fields(fields);

转换为搜索语句为

{
  "multi_match" : {
    "query" : "测试项目",
    "fields" : [
      "columnA^6.0",    //^相当于boost关键词
      "columnB^1.0",
      "columnC^2.0",
      "columnD^4.0",
      "columnE^3.0",
      "columnF^5.0"
    ],
    "type" : "best_fields",
    "operator" : "OR",
    "slop" : 0,
    "prefix_length" : 0,
    "max_expansions" : 50,
    "zero_terms_query" : "NONE",
    "auto_generate_synonyms_phrase_query" : true,
    "fuzzy_transpositions" : true,
    "boost" : 1.0
  }
}

这种写法比较简单统一,在kibana控制台工具上也方便调试。

 

最后注意:

分词后影响查询结果的两个参数minimum_should_match和operator

operator参数表示字段如何匹配分词后的搜索词

operator为or时,查询条件如下

if (doc.eventname contains "key1" or doc.eventname contains "key2" or doc.eventname contains "key3") 

operator为and时,查询条件如下

if (doc.eventname contains "key1" and doc.eventname contains "key2" and doc.eventname contains "key3") 

operator这个对搜索精确度影响很大。

minium_should_match参数表示字段匹配关键词的数量最小限制,可以是数字字符串,也可以是百分比。

 

这里是官方说明:

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#operator-min

对于operator这里说明的很清楚

https://www.cnblogs.com/ljhdo/archive/2017/04/05/4577065.html

 

补充1:

关于多字段搜索匹配多个关键词的查询处理。

multi_match的type有三个 best_fields,most_fields,cross_fields。
其中best_fields,most_fields是以字段为中心的
cross_fields是以词条为中心的

如:查询关键词 “常德市  小明” 分别对应索引文档中的 area, user字段。   area^6,user^4的配置

如果使用best_fields,most_fields,不出意外应该是先将area字段中包含“常德市”的文档先检索出来。

如果使用cross_fields,会将area字段中包含“常德市”,user字段中包含“小明”的文档检索出来。

cross_fields把几个相关字段合并成了一个大字段,进行词频统计,当我们搜索多字段多关键词时,明显这种处理更符合我们的需求。

 

满足这种需求有两种处理方式,一种是在查询阶段,设定查询类型

MultiMatchQueryBuilder entireReg = QueryBuilders.multiMatchQuery(key, "areaName","username")
          .fields(fields)
          .type(Type.CROSS_FIELDS)  //合并字段查询
          .operator(Operator.AND); 

一种是在映射阶段。把相关字段合并成一个大字段:

{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "last_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "full_name": {  //这是合并后的新字段
                    "type":     "string"
                }
            }
        }
    }
}

一切为了满足搜索要求,代码需要相关适配调整。

参考:https://blog.csdn.net/dm_vincent/article/details/41863015(翻译官方原文档)

官方地址:https://www.elastic.co/guide/en/elasticsearch/guide/2.x/multi-field-search.html

 

 

 

 


   原创文章,转载请标明本文链接: ES中使搜索词通过ik分词后匹配多个文档字段,根据字段权重和最大匹配原则进行返回的QueryBuilders创建

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注