我们在弄清楚如何最好地管理我们的标记化和未标记化字段以进行搜索和排序方面遇到了一些困难。我们的目标非常简单:
- 支持部分单词搜索
- 支持对所有字段进行排序
- 我们的映射必须是动态的,客户在运行时添加新字段。
我们能够使用动态模板完成此操作。我们使用默认的分词器、自定义的 ngram 分词器和未分析的分词器来存储字符串。映射:
curl -XPUT 'http://testServer:9200/test/' -d '{
"settings": {
"analysis": {
"analyzer": {
"my_ngram_analyzer": {
"tokenizer": "my_ngram_tokenizer",
"filter": [
"lowercase"
],
"type" : "custom"
},
"default_search": {
"tokenizer" : "keyword",
"filter" : [
"lowercase"
]
}
},
"tokenizer": {
"my_ngram_tokenizer": {
"type": "nGram",
"min_gram": "3",
"max_gram": "100",
"token_chars": []
}
}
}
},
"mappings": {
"TestObject": {
"dynamic_templates": [
{
"metadata_template": {
"match_mapping_type": "string",
"path_match": "*",
"mapping": {
"type": "multi_field",
"fields": {
"ngram": {
"type": "{dynamic_type}",
"index": "analyzed",
"index_analyzer": "my_ngram_analyzer",
"search_analyzer" : "default_search"
},
"{name}": {
"type": "{dynamic_type}",
"index": "analyzed",
"index_analyzer" : "standard",
"search_analyzer" : "default_search"
},
"sortable": {
"type": "{dynamic_type}",
"index": "analyzed",
"analyzer" : "default_search"
}
}
}
}
}
]
}
}
}'
我们实际上只保留未分析的字段以进行排序和精确匹配(我们甚至称之为"可排序"。这种配置使我们很容易获得部分单词搜索,如果查询是"包含"查询 - 我们将".ngram"附加到查询目标。我们遇到的问题是决定何时使用".sortable"后缀。例如,如果我们收到按日期更新排序的请求,我们不想使用 .sortable,因为该字段是日期。如果请求是按"名称"排序,我们确实想使用它,因为该字段是一个字符串,如果我们尝试按"价格"排序,则不使用它。
在排序之前检查字段类型的逻辑似乎有点笨拙(我们检查我们的模型,而不是在 elasticsearch 中检查类型)。总是有一个".sortable"字段会很好,但我们不能通过下面的动态模板运行非字符串类型 - 布尔值和数字不能通过 ngram 过滤器运行。
有没有人建议我们如何始终有一个".sortable"字段进行排序,无论类型如何,它都不会被标记化?或者,对于我们没有看到的此类问题,也许您有更好的解决方案?提前感谢!
这真正归结为,我们一直希望在每个映射字段上都有一个"可排序"字段(我们将其重命名为"未分析",因为它有其他用途)。这样做的真正诀窍是,在不为每个类型添加新的动态模板的情况下,创建一个适用于除字符串以外的每种类型的动态模板。为此,您需要将match_pattern
设置为正则表达式:
{
"other_types": {
"match_mapping_type": "date|boolean|double|long|integer",
"match_pattern": "regex",
"path_match": ".*",
"mapping": {
"type": "multi_field",
"fields": {
"{name}": {
"type": "{dynamic_type}",
"index": "not_analyzed"
},
"unanalyzed": {
"type": "{dynamic_type}",
"index": "not_analyzed"
}
}
}
}
}
请注意,您还需要对"path_match"进行小的更改 - 您必须使用真正的正则表达式(与 ES "简单"表达式的"*"相反)。
这样做的一个缺点是我们增加了索引的大小 - 我们将所有这些类型存储两次。不过,出于我们的目的,我们的索引(我们有很多)有足够的增长空间,值得避免在执行排序或完全匹配查询之前对每个字段进行类型查找(只是总是使用 ${fieldName}.unanalyzed)。