【问题标题】:Word-oriented completion suggester (ElasticSearch 5.x)面向单词的补全建议器 (ElasticSearch 5.x)
【发布时间】:2017-06-04 07:47:19
【问题描述】:

ElasticSearch 5.x 对 Suggester API (Documentation) 进行了一些(重大)更改。最显着的变化如下:

完成建议是面向文档的

建议都知道 他们所属的文件。现在,相关文档 (_source) 是 作为完成建议的一部分返回。

简而言之,所有完成查询都会返回所有匹配的文档,而不是只返回匹配的单词。这就是问题所在 - 如果自动完成的单词出现在多个文档中,则会出现重复。

假设我们有这个简单的映射:

{
   "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) 个 文档 - John 的文档和两者都具有相同的 text 属性值。

但是,我希望收到一 (1) 个单词。像这样简单的事情:

{
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "my-suggest": [
      {
         "text": "joh",
         "offset": 0,
         "length": 3,
         "options": [
          "John"
         ]
      }
   ]
}

问题:如何实现基于单词的完成提示。无需返回任何与文档相关的数据,因为我现在不需要它。

“完成建议”是否适合我的场景?还是应该使用完全不同的方法?


编辑: 正如你们中的许多人所指出的,额外的仅完成索引将是一个可行的解决方案。但是,我可以看到这种方法存在多个问题:

  1. 保持新索引同步。
  2. 自动完成后续单词可能是全局的,而不是缩小范围。例如,假设您在附加索引中有以下单词:"John", "Doe", "David", "Smith"。查询"John D"时,不完整单词的结果应该是"Doe"而不是"Doe", "David"

要克服第二点,仅索引单个单词是不够的,因为您还需要将所有单词映射到文档,以便正确缩小自动完成后续单词的范围。有了这个,你实际上和查询原始索引有同样的问题。因此,附加索引不再有意义。

【问题讨论】:

  • 正如in this issue 所暗示的那样,这种新行为是“设计使然”,并且没有改变它的计划。他们的建议是为完成建议创建另一个索引。与下面@EdgarVonk 的建议差不多。
  • 对当前索引的自定义查询呢?也许为具有不同查询(具有术语聚合)的所有建议创建一个额外的 NGram 字段?至于额外的仅建议索引,我可以确定一些问题,这些问题实际上与您提出的解决方案相矛盾(请参阅我更新的问题)。
  • 当然,术语聚合也可以实现类似的目标,但这取决于您拥有的文档负载。我不是提出那个解决方案,Edgar 和 ES 人(见问题)是 ;-)

标签: elasticsearch autocomplete duplicates elasticsearch-5


【解决方案1】:

正如评论中所暗示的,在不获取重复文档的情况下实现此目的的另一种方法是为 firstname 字段创建一个子字段,其中包含该字段的 ngram。首先你像这样定义你的映射:

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

【讨论】:

  • 如果您想搜索多个字段怎么办?理想情况下,我会有一个名为 suggest 的附加多值字段,其中包含我想要自动完成的所有值(姓名、姓氏、用户名、电子邮件等)。
  • 那是同一回事,该字段中包含的每个标记都将被索引
  • 但是聚合是否也可以删除重复条目?
  • 在术语聚合中,每个匹配术语只会出现一次。在上面的聚合中你不可能得到两个 John 桶。
  • 您的原型可以工作,但我需要进行性能测试,看看这个解决方案是否可行。另外,您建议如何考虑后续单词?这意味着输入“johnny d”应该产生DoeDeere,因为没有johnny有那个姓氏?
【解决方案2】:

下一个版本 6.x 中将添加一个额外的字段 skip_duplicates。

来自https://www.elastic.co/guide/en/elasticsearch/reference/master/search-suggesters-completion.html#skip_duplicates 的文档:

POST music/_search?pretty
{
    "suggest": {
        "song-suggest" : {
            "prefix" : "nor",
            "completion" : {
                "field" : "suggest",
                "skip_duplicates": true
            }
        }
    }
}

【讨论】:

  • 请注意"skip_duplicates": true 就像一个魅力,但仅适用于 ES6.1,它可能是最好的解决方案。对于 ES6.0,这是我的情况,它不起作用。
【解决方案3】:

我们面临完全相同的问题。在 Elasticsearch 2.4 中,您所描述的方法过去对我们来说效果很好,但现在正如您所说,建议者已经成为基于文档的,而像您一样,我们只对独特的词感兴趣,而不是对文档感兴趣。

到目前为止,我们能想到的唯一“解决方案”是为我们想要执行建议查询的单词创建一个单独的索引,并在这个单独的索引中确保以某种方式只为相同的单词编制一次索引。然后您可以对这个单独的索引执行建议查询。这远非理想,因为我们需要确保该索引与其他查询所需的其他索引保持同步。

【讨论】:

  • 您能否详细说明如何创建一种机制来保持此索引同步?以及如何避免对后续单词的全局建议?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
  • 2015-03-12
相关资源
最近更新 更多