面向单词的完成建议器 (ElasticSearch 5.x)
Word-oriented completion suggester (ElasticSearch 5.x)
ElasticSearch 5.x 对 Suggester API (Documentation) 引入了一些(重大)更改。最显着的变化如下:
Completion suggester is document-oriented
Suggestions are aware of the
document they belong to. Now, associated documents (_source
) are
returned as part of completion suggestions.
简而言之,所有完成查询 return 所有匹配 文档 而不是仅匹配 单词 。这就是问题所在 - 如果自动完成的单词出现在多个文档中,则会出现重复。
假设我们有这个简单的映射:
{
"my-index": {
"mappings": {
"users": {
"properties": {
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
},
"suggest": {
"type": "completion",
"analyzer": "simple"
}
}
}
}
}
}
加上几个测试文档:
{
"_index": "my-index",
"_type": "users",
"_id": "1",
"_source": {
"firstName": "John",
"lastName": "Doe",
"suggest": [
{
"input": [
"John",
"Doe"
]
}
]
}
},
{
"_index": "my-index",
"_type": "users",
"_id": "2",
"_source": {
"firstName": "John",
"lastName": "Smith",
"suggest": [
{
"input": [
"John",
"Smith"
]
}
]
}
}
以及按书查询:
POST /my-index/_suggest?pretty
{
"my-suggest" : {
"text" : "joh",
"completion" : {
"field" : "suggest"
}
}
}
产生以下结果:
{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"my-suggest": [
{
"text": "joh",
"offset": 0,
"length": 3,
"options": [
{
"text": "John",
"_index": "my-index",
"_type": "users",
"_id": "1",
"_score": 1,
"_source": {
"firstName": "John",
"lastName": "Doe",
"suggest": [
{
"input": [
"John",
"Doe"
]
}
]
}
},
{
"text": "John",
"_index": "my-index",
"_type": "users",
"_id": "2",
"_score": 1,
"_source": {
"firstName": "John",
"lastName": "Smith",
"suggest": [
{
"input": [
"John",
"Smith"
]
}
]
}
}
]
}
]
}
简而言之,对于文本 "joh" 的完成建议,两 (2) 个 文档 被 returned - John 和两者都有相同的text
属性.
的值
不过,我想收到一 (1) 个 word。像这样简单的东西:
{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"my-suggest": [
{
"text": "joh",
"offset": 0,
"length": 3,
"options": [
"John"
]
}
]
}
问题:如何实现基于单词的补全建议器。不需要 return 任何文档相关数据,因为此时我不需要它。
"Completion Suggester" 是否适合我的场景?或者我应该使用完全不同的方法?
编辑:
正如你们中的许多人指出的那样,一个额外的仅完成索引将是一个可行的解决方案。但是,我可以看到这种方法存在多个问题:
- 保持新索引同步。
- 自动完成后续单词可能是全局的,而不是缩小范围。例如,假设您在附加索引中有以下单词:
"John", "Doe", "David", "Smith"
。当查询 "John D"
时,不完整单词的结果应该是 "Doe"
而不是 "Doe", "David"
.
要克服第二点,仅索引单个单词是不够的,因为您还需要将所有单词映射到文档,以便正确缩小自动完成后续单词的范围。这样,您实际上遇到了与查询原始索引相同的问题。因此,附加索引不再有意义。
我们面临完全相同的问题。在 Elasticsearch 2.4 中,您描述的方法过去对我们来说效果很好,但现在正如您所说,建议者已变成 document-based 而像您一样,我们只对独特的单词感兴趣,而不对文档感兴趣。
到目前为止,我们唯一能想到的 'solution' 就是为我们要执行建议查询的词创建一个单独的索引,并在这个单独的索引中以某种方式确保相同的词只索引一次。然后您可以在这个单独的索引上执行建议查询。这远非理想,只是因为我们需要确保该索引与我们其他查询所需的其他索引保持同步。
正如评论中所暗示的那样,在不获取重复文档的情况下实现此目的的另一种方法是为包含该字段的 ngram 的 firstname
字段创建一个 sub-field。首先你定义你的映射是这样的:
PUT my-index
{
"settings": {
"analysis": {
"analyzer": {
"completion_analyzer": {
"type": "custom",
"filter": [
"lowercase",
"completion_filter"
],
"tokenizer": "keyword"
}
},
"filter": {
"completion_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 24
}
}
}
},
"mappings": {
"users": {
"properties": {
"autocomplete": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
},
"completion": {
"type": "text",
"analyzer": "completion_analyzer",
"search_analyzer": "standard"
}
}
},
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
}
}
}
}
}
然后你索引几个文档:
POST my-index/users/_bulk
{"index":{}}
{ "firstName": "John", "lastName": "Doe", "autocomplete": "John Doe"}
{"index":{}}
{ "firstName": "John", "lastName": "Deere", "autocomplete": "John Deere" }
{"index":{}}
{ "firstName": "Johnny", "lastName": "Cash", "autocomplete": "Johnny Cash" }
然后您可以查询 joh
并获得 John
的一个结果和 Johnny
的另一个结果
{
"size": 0,
"query": {
"term": {
"autocomplete.completion": "john d"
}
},
"aggs": {
"suggestions": {
"terms": {
"field": "autocomplete.raw"
}
}
}
}
结果:
{
"aggregations": {
"suggestions": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "John Doe",
"doc_count": 1
},
{
"key": "John Deere",
"doc_count": 1
}
]
}
}
}
更新(2019 年 6 月 25 日):
ES 7.2 引入了一种名为 search_as_you_type
的新数据类型,它本身就允许这种行为。阅读更多:https://www.elastic.co/guide/en/elasticsearch/reference/7.2/search-as-you-type.html
将在下一版本 6.x 中添加一个附加字段 skip_duplicates。
POST music/_search?pretty
{
"suggest": {
"song-suggest" : {
"prefix" : "nor",
"completion" : {
"field" : "suggest",
"skip_duplicates": true
}
}
}
}
ElasticSearch 5.x 对 Suggester API (Documentation) 引入了一些(重大)更改。最显着的变化如下:
Completion suggester is document-oriented
Suggestions are aware of the document they belong to. Now, associated documents (
_source
) are returned as part of completion suggestions.
简而言之,所有完成查询 return 所有匹配 文档 而不是仅匹配 单词 。这就是问题所在 - 如果自动完成的单词出现在多个文档中,则会出现重复。
假设我们有这个简单的映射:
{
"my-index": {
"mappings": {
"users": {
"properties": {
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
},
"suggest": {
"type": "completion",
"analyzer": "simple"
}
}
}
}
}
}
加上几个测试文档:
{
"_index": "my-index",
"_type": "users",
"_id": "1",
"_source": {
"firstName": "John",
"lastName": "Doe",
"suggest": [
{
"input": [
"John",
"Doe"
]
}
]
}
},
{
"_index": "my-index",
"_type": "users",
"_id": "2",
"_source": {
"firstName": "John",
"lastName": "Smith",
"suggest": [
{
"input": [
"John",
"Smith"
]
}
]
}
}
以及按书查询:
POST /my-index/_suggest?pretty
{
"my-suggest" : {
"text" : "joh",
"completion" : {
"field" : "suggest"
}
}
}
产生以下结果:
{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"my-suggest": [
{
"text": "joh",
"offset": 0,
"length": 3,
"options": [
{
"text": "John",
"_index": "my-index",
"_type": "users",
"_id": "1",
"_score": 1,
"_source": {
"firstName": "John",
"lastName": "Doe",
"suggest": [
{
"input": [
"John",
"Doe"
]
}
]
}
},
{
"text": "John",
"_index": "my-index",
"_type": "users",
"_id": "2",
"_score": 1,
"_source": {
"firstName": "John",
"lastName": "Smith",
"suggest": [
{
"input": [
"John",
"Smith"
]
}
]
}
}
]
}
]
}
简而言之,对于文本 "joh" 的完成建议,两 (2) 个 文档 被 returned - John 和两者都有相同的text
属性.
不过,我想收到一 (1) 个 word。像这样简单的东西:
{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"my-suggest": [
{
"text": "joh",
"offset": 0,
"length": 3,
"options": [
"John"
]
}
]
}
问题:如何实现基于单词的补全建议器。不需要 return 任何文档相关数据,因为此时我不需要它。
"Completion Suggester" 是否适合我的场景?或者我应该使用完全不同的方法?
编辑: 正如你们中的许多人指出的那样,一个额外的仅完成索引将是一个可行的解决方案。但是,我可以看到这种方法存在多个问题:
- 保持新索引同步。
- 自动完成后续单词可能是全局的,而不是缩小范围。例如,假设您在附加索引中有以下单词:
"John", "Doe", "David", "Smith"
。当查询"John D"
时,不完整单词的结果应该是"Doe"
而不是"Doe", "David"
.
要克服第二点,仅索引单个单词是不够的,因为您还需要将所有单词映射到文档,以便正确缩小自动完成后续单词的范围。这样,您实际上遇到了与查询原始索引相同的问题。因此,附加索引不再有意义。
我们面临完全相同的问题。在 Elasticsearch 2.4 中,您描述的方法过去对我们来说效果很好,但现在正如您所说,建议者已变成 document-based 而像您一样,我们只对独特的单词感兴趣,而不对文档感兴趣。
到目前为止,我们唯一能想到的 'solution' 就是为我们要执行建议查询的词创建一个单独的索引,并在这个单独的索引中以某种方式确保相同的词只索引一次。然后您可以在这个单独的索引上执行建议查询。这远非理想,只是因为我们需要确保该索引与我们其他查询所需的其他索引保持同步。
正如评论中所暗示的那样,在不获取重复文档的情况下实现此目的的另一种方法是为包含该字段的 ngram 的 firstname
字段创建一个 sub-field。首先你定义你的映射是这样的:
PUT my-index
{
"settings": {
"analysis": {
"analyzer": {
"completion_analyzer": {
"type": "custom",
"filter": [
"lowercase",
"completion_filter"
],
"tokenizer": "keyword"
}
},
"filter": {
"completion_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 24
}
}
}
},
"mappings": {
"users": {
"properties": {
"autocomplete": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
},
"completion": {
"type": "text",
"analyzer": "completion_analyzer",
"search_analyzer": "standard"
}
}
},
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
}
}
}
}
}
然后你索引几个文档:
POST my-index/users/_bulk
{"index":{}}
{ "firstName": "John", "lastName": "Doe", "autocomplete": "John Doe"}
{"index":{}}
{ "firstName": "John", "lastName": "Deere", "autocomplete": "John Deere" }
{"index":{}}
{ "firstName": "Johnny", "lastName": "Cash", "autocomplete": "Johnny Cash" }
然后您可以查询 joh
并获得 John
的一个结果和 Johnny
{
"size": 0,
"query": {
"term": {
"autocomplete.completion": "john d"
}
},
"aggs": {
"suggestions": {
"terms": {
"field": "autocomplete.raw"
}
}
}
}
结果:
{
"aggregations": {
"suggestions": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "John Doe",
"doc_count": 1
},
{
"key": "John Deere",
"doc_count": 1
}
]
}
}
}
更新(2019 年 6 月 25 日):
ES 7.2 引入了一种名为 search_as_you_type
的新数据类型,它本身就允许这种行为。阅读更多:https://www.elastic.co/guide/en/elasticsearch/reference/7.2/search-as-you-type.html
将在下一版本 6.x 中添加一个附加字段 skip_duplicates。
POST music/_search?pretty
{
"suggest": {
"song-suggest" : {
"prefix" : "nor",
"completion" : {
"field" : "suggest",
"skip_duplicates": true
}
}
}
}