Elasticsearch如何对copy_to生成的字段实现多词精准匹配搜索
我目前正在学习Elasticsearch,在现有索引上新增了如下mapping配置,打算通过copy_to把原有字段聚合到新字段做统一搜索:
PUT user-index { "mappings": { "properties": { "common_criteria": { // 新增字段,通过copy_to聚合其他字段值 "type": "text" }, "name": { // 配置新增前已存在的字段 "type": "text", "copy_to": "common_criteria" }, "username": { // 配置新增前已存在的字段 "type": "text", "copy_to": "common_criteria" }, "phone": { // 配置新增前已存在的字段 "type": "text", "copy_to": "common_criteria" }, "country": { // 配置新增前已存在的字段 "type": "text", "copy_to": "common_criteria" } } } }
新增的common_criteria为text类型,用来聚合原有name/username/phone/country四个字段的值。我的目标是仅在这个字段上做单值/多值精准匹配,假设存在测试文档:
{ "common_criteria": ["John Smith","johny","USA"] }
需要满足的匹配规则:
- 搜索词为
John Smith、USA + John Smith、johny + USA、USA、johny、John Smith + USA + johny时都能命中文档,搜索词顺序不影响结果 - 搜索词包含字段不存在的值时,比如
John Smith + Germany、johny + England,不能命中文档
我用Spring Data Elastic编写的初始查询代码如下:
NativeSearchQueryBuilder nativeSearchQuery = new NativeSearchQueryBuilder(); BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery(); String valueToSearch = "johny" nativeSearchQuery.withQuery(booleanQuery.must(QueryBuilders.matchQuery("common_criteria", valueToSearch) .fuzziness(Fuzziness.AUTO) .operator(Operator.AND)));
实际发往Elasticsearch的请求体:
{ "bool" : { "must" : { "match" : { "common_criteria" : { "query" : "johny", "operator" : "AND", "fuzziness" : "AUTO", "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 } }
用这个查询搜johny返回0条结果,我判断要么是must.match的逻辑有问题,要么是common_criteria的字段定义不对。
补充:尝试multi_match的结果
按照建议试了multi_match查询还是不行,发往ES的请求如下(依然返回0条结果):
{ "bool" : { "must" : { "multi_match" : { "query" : "John Smith USA", "fields" : [ "name^1.0", "username^1.0", "phone^1.0", "country^1.0" ], "type" : "best_fields", "operator" : "AND", "slop" : 0, "fuzziness" : "AUTO", "prefix_length" : 0, "max_expansions" : 50, "zero_terms_query" : "NONE", "auto_generate_synonyms_phrase_query" : true, "fuzzy_transpositions" : true, "boost" : 1.0 } }, "adjust_pure_negative" : true, "boost" : 1.0 } }
搜不到结果、匹配逻辑不符合预期的核心原因有两个:
copy_to的机制坑(直接导致搜johny返回0条)
copy_to是文档写入阶段的处理逻辑,只会作用在mapping修改完成后新写入、或者更新过的文档,配置修改前就存在的存量文档,不会自动把原有字段的值回填到新的common_criteria里。你需要先执行一次update_by_query触发存量文档重建,把值同步到common_criteria字段:POST user-index/_update_by_query?conflicts=proceed { "query": { "match_all": {} } }执行完可以先查一下文档,确认
common_criteria字段已经有值,再做后续查询测试。字段类型和查询逻辑不满足匹配规则
你现在common_criteria用的是默认text类型+标准分词器,写入John Smith的时候会被拆成john、smith两个独立的词项存到倒排索引里,直接用match查询搜的是分词后的词,根本没法判断传入的完整搜索值(比如整个John Smith、整个USA)是否存在,自然实现不了「带不存在的词就不返回」的要求。
你需要给common_criteria加一个keyword子字段,存不分词的原始值:PUT user-index/_mapping { "properties": { "common_criteria": { "type": "text", "fields": { "keyword": { "type": "keyword" } } } } }加完之后再执行一次上面的
_update_by_query回填数据即可。
查询的时候不要直接用match查text字段,先把传入的搜索词按分隔符(比如+号)拆成独立的关键词项,每个项用term查询匹配common_criteria.keyword字段,全部放到bool.must里就能完全满足你的规则:
- 所有传入的关键词都必须在
common_criteria的原始值里存在,少一个、多传不存在的值都不会命中 - 顺序不影响结果,
bool.must本身是无序匹配 - 如果需要保留你之前开的模糊匹配能力,把
term换成fuzzy查询即可。
对应的Spring Data Elastic代码逻辑参考:
BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery(); // 把搜索字符串按分隔符拆成独立关键词,比如把"John Smith + USA"拆成["John Smith", "USA"] String[] searchItems = valueToSearch.split("\\s*\\+\\s*"); for (String item : searchItems) { // 精准匹配用term,要模糊匹配就换成fuzzyQuery booleanQuery.must(QueryBuilders.termQuery("common_criteria.keyword", item.trim())); // 模糊匹配示例:booleanQuery.must(QueryBuilders.fuzzyQuery("common_criteria.keyword", item.trim()).fuzziness(Fuzziness.AUTO)); } nativeSearchQuery.withQuery(booleanQuery);
之前试的multi_match查询不生效,一方面是存量数据没回填的问题没解决,另一方面multi_match是跨多个原字段匹配,只要任意一个字段命中分词后的词就会返回,根本做不到「所有传入的搜索值都必须存在,出现不存在的值就过滤」的要求。
内容的提问来源于stack exchange,提问作者akuma8

