如何在Elasticsearch/OpenSearch中实现特定词组整体分析且match查询仅匹配独立词汇的需求
当然可以搞定这个需求!我之前做销售系统搜索功能时也遇到过几乎一模一样的场景——就是要让特定词组(比如你说的"Sales Play")作为一个不可拆分的整体被索引,单独搜"sale"的时候只能匹配那些真正独立的"sale"词汇,而不会把包含这个词组的文档也拉出来。
核心思路很简单:通过自定义分析器,把你不想拆分的词组在索引阶段就合并成一个单一的token,这样普通的单个词查询就不会命中这些合并后的token了。下面给你一步步拆解具体操作:
第一步:创建带自定义分析器的索引
首先你需要创建一个包含自定义分析器的索引,这个分析器会专门处理你指定的词组,把它们合并成无空格的整体,同时保证普通词汇正常分词:
PUT /sales_docs { "settings": { "analysis": { "char_filter": { // 定义字符过滤器,匹配要保留的词组并合并 "merge_target_phrases": { "type": "pattern_replace", // 这里可以添加多个你想保留的词组,用|分隔 "pattern": "(Sales)\\s+(Play)", "replacement": "$1$2" } }, "analyzer": { "custom_phrase_analyzer": { "type": "custom", "char_filter": ["merge_target_phrases"], "tokenizer": "standard", // 普通词汇用标准分词器拆分 "filter": ["lowercase"] // 统一大小写,避免大小写不匹配 } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "custom_phrase_analyzer" // 给title字段用这个自定义分析器 } } } }
如果之后需要添加更多要保留的词组,比如"Customer Success",只需要把pattern改成"(Sales)\\s+(Play)|(Customer)\\s+(Success)",对应的replacement改成"$1$2$3$4"就行,非常灵活。
第二步:索引你的测试文档
接下来把你提到的两个文档索引进去:
// 索引文档1:包含词组"Sales Play" POST /sales_docs/_doc/1 { "title": "Sales play" } // 索引文档2:只有独立的"Sales" POST /sales_docs/_doc/2 { "title": "Sales" }
第三步:执行查询验证
现在执行你想要的match查询,看看结果是不是符合预期:
GET /sales_docs/_search { "query": { "match": { "title": "sales" } } }
这时候你会发现,只有文档2会被返回——因为文档1的"Sales play"在索引时被转换成了salesplay这个单一token,而查询的sales只会匹配文档2里的sales token,完全符合你的要求!
额外小技巧:兼顾多种查询需求
如果你有时候需要普通的分词查询(比如搜"play"能找到文档1),可以给title字段设置多字段(multi-field),同时保留标准分词的版本:
"title": { "type": "text", "fields": { "phrase": { "type": "text", "analyzer": "custom_phrase_analyzer" }, "standard": { "type": "text", "analyzer": "standard" } } }
之后要匹配独立词汇就查title.phrase,要普通分词查询就查title.standard,一举两得。
内容来源于stack exchange

