如何配置Elasticsearch使multi_match匹配字母数字组合的空格与无空格形式?
问题翻译
我在使用Elasticsearch时,索引中存在类似“Hotel101 fort”的字母数字组合条目。当用“Hotel 101 fort”查询时无法得到正确结果,因为Elasticsearch将查询拆分为“Hotel”“101”“fort”三个token,而文档被分词为“Hotel101”和“fort”。我的目标是让文档中的“Hotel101”匹配这类查询,且无需每次查询显式指定analyzer。目前已尝试使用multi_match跨多字段查询,以及包含ngram和synonym过滤器的自定义analyzer,但ngram无法解决完整分词问题,synonym过滤器需手动添加所有组合,效率极低。请问如何配置Elasticsearch,使“Hotel101”和“Hotel 101”在索引和查询时被视为同一token,且无需每次查询指定analyzer?
要让带空格和不带空格的字母数字组合(如Hotel 101和Hotel101)被视为同一token,无需每次查询指定分析器,你可以通过自定义分析器+字符过滤器的方式,在索引和查询阶段统一处理文本格式,具体步骤如下:
1. 创建带字符过滤器的自定义分析器
核心思路是:用pattern_replace字符过滤器,在索引和查询时自动移除字母与数字之间的空格,将两种格式统一为无空格的形式,确保分词后生成完全一致的token。
创建索引的完整配置如下:
PUT /your_index_name { "settings": { "analysis": { "char_filter": { // 移除字母与数字、数字与字母之间的空格 "alnum_space_normalizer": { "type": "pattern_replace", "pattern": "([a-zA-Z])\\s+(\\d)|(\\d)\\s+([a-zA-Z])", "replacement": "$1$2$3$4" } }, "analyzer": { "unified_alnum_analyzer": { "type": "custom", "char_filter": ["alnum_space_normalizer"], "tokenizer": "standard", "filter": ["lowercase"] } } } }, "mappings": { "properties": { "your_field_name": { "type": "text", // 索引和查询都使用自定义分析器 "analyzer": "unified_alnum_analyzer", "search_analyzer": "unified_alnum_analyzer" } } } }
2. 配置说明
- 字符过滤器
alnum_space_normalizer:通过正则匹配两种场景:- 字母后接空格再接数字(如
Hotel 101→Hotel101) - 数字后接空格再接字母(如
101 Hotel→101Hotel)
自动移除空格,统一格式。
- 字母后接空格再接数字(如
- 自定义分析器
unified_alnum_analyzer:先经过字符过滤器处理,再用标准分词器拆分,最后转小写,确保大小写不影响匹配。 - 字段映射:将目标字段的
analyzer和search_analyzer都指定为自定义分析器,这样查询时无需额外指定,Elasticsearch会自动用该分析器处理查询文本。
3. 测试验证
索引文档
POST /your_index_name/_doc/1 { "your_field_name": "Hotel101 fort" }
执行查询
GET /your_index_name/_search { "query": { "match": { "your_field_name": "Hotel 101 fort" } } }
此时查询文本会被处理为Hotel101 fort,分词后得到hotel101和fort,与文档的分词结果完全一致,因此能匹配到目标文档。
备选方案:使用word_delimiter_graph过滤器
如果你希望同时保留原始组合和拆分后的token(比如既支持Hotel101也支持Hotel+101的查询),可以用word_delimiter_graph过滤器,它会自动拆分字母数字组合并保留原始token:
PUT /your_index_name { "settings": { "analysis": { "analyzer": { "alnum_split_analyzer": { "type": "custom", "tokenizer": "standard", "filter": ["custom_word_delimiter", "lowercase"] } }, "filter": { "custom_word_delimiter": { "type": "word_delimiter_graph", "preserve_original": true, // 保留原始token(如Hotel101) "split_on_numerics": true, // 按数字拆分 "split_on_case_change": true // 按大小写变化拆分 } } } }, "mappings": { "properties": { "your_field_name": { "type": "text", "analyzer": "alnum_split_analyzer", "search_analyzer": "alnum_split_analyzer" } } } }
这种配置下,文档中的Hotel101会被拆分为hotel、101、hotel101三个token,查询Hotel 101 fort时生成的hotel、101、fort能与文档中的token匹配,同样能得到正确结果。
内容的提问来源于stack exchange,提问作者A_xay Queuemar

