这可能是一个非常常见的问题,但到目前为止我得到的答案并不令人满意 .
问题:我有一个由近100个字段组成的es索引 . 大多数字段是 string
类型并设置为 analyzed
. 但是,查询可以是部分( match
)或精确(更像是 term
) . 因此,如果我的索引包含值为 super duper cool pizza
的字符串字段,则可能存在部分查询,如 duper super
,并且将与文档匹配,但是,可能存在与 cool pizza
不同的精确查询,该查询与文档不匹配 . 另一方面, Super Duper COOL PIzza
应该再次与本文档相匹配 .
到目前为止,部分匹配部分很简单,我在 match
查询中使用 AND
运算符 . 但是无法完成其他类型的操作 .
我查看了与此问题相关的其他帖子,此帖子包含最接近的解决方案:Elasticsearch exact matches on analyzed fields
在这三个解决方案中,第一个解决方案感觉非常复杂,因为我有很多字段而且我不使用REST api,我使用带有来自Java api的NativeSearchQueryBuilder的QueryBuilders动态创建查询 . 它还会产生许多可能的模式,我认为这会导致性能问题 .
第二个是一个更容易的解决方案,但同样,我必须保持更多(几乎)冗余数据,我不认为使用 term
查询将解决我的问题 .
我认为最后一个有问题,它不会阻止 super duper
与 super duper cool pizza
匹配,这不是我想要的输出 .
那么有什么其他方法可以实现这个目标吗?如果需要清除问题,我可以发布一些样本映射 . 我已经保留了源代码(如果可以使用的话) . 请随时提出任何改进建议 .
提前致谢 .
[ UPDATE ]
最后,我使用 multi_field
,保留原始字段以进行精确查询 . 当我插入时,我对数据使用一些自定义修改,并且在搜索期间,我在输入文本上使用了相同的修改例程 . 此部分不由Elasticsearch处理 . 如果您想这样做,您还必须设计合适的分析仪 .
索引设置和映射查询:
PUT test_index
POST test_index/_close
PUT test_index/_settings
{
"index": {
"analysis": {
"analyzer": {
"standard_uppercase": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "keyword",
"filter": ["uppercase"]
}
}
}
}
}
PUT test_index/doc/_mapping
{
"doc": {
"properties": {
"text_field": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"analyzer": "standard_uppercase"
}
}
}
}
}
}
POST test_index/_open
插入一些样本数据:
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"text_field":"super duper cool pizza"}
{"index":{"_id":2}}
{"text_field":"some other text"}
{"index":{"_id":3}}
{"text_field":"pizza"}
确切的查询:
GET test_index/doc/_search
{
"query": {
"bool": {
"must": {
"bool": {
"should": {
"term": {
"text_field.raw": "PIZZA"
}
}
}
}
}
}
}
响应:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.4054651,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "3",
"_score": 1.4054651,
"_source": {
"text_field": "pizza"
}
}
]
}
}
部分查询:
GET test_index/doc/_search
{
"query": {
"bool": {
"must": {
"bool": {
"should": {
"match": {
"text_field": {
"query": "pizza",
"operator": "AND",
"type": "boolean"
}
}
}
}
}
}
}
}
响应:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "3",
"_score": 1,
"_source": {
"text_field": "pizza"
}
},
{
"_index": "test_index",
"_type": "doc",
"_id": "1",
"_score": 0.5,
"_source": {
"text_field": "super duper cool pizza"
}
}
]
}
}
PS:这些是生成的查询,这就是为什么有一些冗余块,因为会有许多其他字段连接到查询中 .
可悲的是,现在我需要再次重写整个映射:(
1 回答
我认为这会做你想要的(或者至少尽可能接近),使用keyword tokenizer和lowercase token filter:
我添加了几个用于测试的文档:
请注意,我们将外部
text_field
设置为由标准分析器进行分析,然后是raw
的子字段raw
(您可能不需要这个,我只是将其添加用于比较),另一个子字段lowercase
创建令牌恰好是与输入文本相同,除了它们是小写的(但不是在空格上分割) . 所以match
查询会返回您的预期:请记住,
match
查询也将使用字段的分析器对照搜索短语,因此在这种情况下搜索"super duper cool pizza"
将与搜索"Super Duper COOL PIzza"
具有完全相同的效果(如果您想要完全匹配,您仍然可以使用term
查询) .查看三个文档在每个字段中生成的术语非常有用,因为这是您的搜索查询将要反对的内容(在这种情况下
raw
和lowercase
具有相同的标记,但这只是因为所有输入都较低 - 已经):这是我用来测试它的代码:
http://sense.qbox.io/gist/cc7564464cec88dd7f9e6d9d7cfccca2f564fde1
如果你还想做部分单词匹配,我建议你看看ngrams . 我在这里写了一篇关于Qbox的介绍:
https://qbox.io/blog/an-introduction-to-ngrams-in-elasticsearch