如何使用 ElasticSearch 搜索单词的一部分

我最近开始使用 ElasticSearch,但似乎无法让它搜索单词的一部分。

示例: 在 ElasticSearch 中,我有三个来自 couchdb 的索引文档:

{
"_id" : "1",
"name" : "John Doeman",
"function" : "Janitor"
}
{
"_id" : "2",
"name" : "Jane Doewoman",
"function" : "Teacher"
}
{
"_id" : "3",
"name" : "Jimmy Jackal",
"function" : "Student"
}

现在,我要搜索所有包含“ Doe”的文档

curl http://localhost:9200/my_idx/my_type/_search?q=Doe

没有任何结果,但如果我搜索

curl http://localhost:9200/my_idx/my_type/_search?q=Doeman

它确实返回了一份文件(约翰杜曼)。

我尝试过将不同的分析器和过滤器设置为索引的属性。我还尝试使用一个完整的查询(例如:

{
"query": {
"term": {
"name": "Doe"
}
}
}

) 但似乎没什么效果。

当我搜索“ Doe”时,如何让 ElasticSearch 同时找到 John Doeman 和 Jane Doewoman?

更新

我尝试使用 nGram 标记器和过滤器,就像 Igor 建议的那样,像这样:

{
"index": {
"index": "my_idx",
"type": "my_type",
"bulk_size": "100",
"bulk_timeout": "10ms",
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "my_ngram_tokenizer",
"filter": [
"my_ngram_filter"
]
}
},
"filter": {
"my_ngram_filter": {
"type": "nGram",
"min_gram": 1,
"max_gram": 1
}
},
"tokenizer": {
"my_ngram_tokenizer": {
"type": "nGram",
"min_gram": 1,
"max_gram": 1
}
}
}
}
}

我现在遇到的问题是,每个查询都返回所有文档。 有什么建议吗? 使用 nGram 的 ElasticSearch 文档不是很好..。

158878 次浏览

算了。

我看了 Lucene 的文件。 似乎我可以使用通配符! : -)

curl http://localhost:9200/my_idx/my_type/_search?q=*Doe*

有用!

在大型索引中,使用前导通配符和尾随通配符进行搜索将会非常慢。如果希望能够通过单词前缀进行搜索,请删除前导通配符。如果您确实需要在一个单词的中间找到一个子字符串,那么最好使用 ngram tokenizer。

我也使用 nGram。我使用标准的 tokenizer 和 nGram 作为过滤器。下面是我的设置:

{
"index": {
"index": "my_idx",
"type": "my_type",
"analysis": {
"index_analyzer": {
"my_index_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"mynGram"
]
}
},
"search_analyzer": {
"my_search_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"standard",
"lowercase",
"mynGram"
]
}
},
"filter": {
"mynGram": {
"type": "nGram",
"min_gram": 2,
"max_gram": 50
}
}
}
}
}

让我们找到50个字母的单词部分。根据需要调整 max _ gram。在德语中可以变得非常大,所以我把它设置为一个很高的值。

尝试使用这里描述的解决方案: ElasticSearch 中的精确子串搜索

{
"mappings": {
"my_type": {
"index_analyzer":"index_ngram",
"search_analyzer":"search_ngram"
}
},
"settings": {
"analysis": {
"filter": {
"ngram_filter": {
"type": "ngram",
"min_gram": 3,
"max_gram": 8
}
},
"analyzer": {
"index_ngram": {
"type": "custom",
"tokenizer": "keyword",
"filter": [ "ngram_filter", "lowercase" ]
},
"search_ngram": {
"type": "custom",
"tokenizer": "keyword",
"filter": "lowercase"
}
}
}
}
}

为了解决磁盘使用问题和搜索条件太长的问题,使用了8个字符长的 英格拉姆(配置为: “ max _ gram”: 8)。若要搜索超过8个字符的术语,请将搜索转换为一个布尔 AND 查询,查找该字符串中每个不同的8个字符的子字符串。例如,如果用户搜索 大院子(一个10个字符的字符串) ,搜索将是:

“ arge ya AND arge yar AND rge yard”。

在不改变索引映射的情况下,您可以执行一个简单的前缀查询,该查询将执行您所希望的部分搜索

也就是说。

{
"query": {
"prefix" : { "name" : "Doe" }
}
}

Https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html

我认为没有必要改变任何映射。 尝试使用 Query _ string,它是完美的。所有的场景都可以使用默认的标准分析器:

我们有数据:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

情况1:

{"query": {
"query_string" : {"default_field" : "name", "query" : "*Doe*"}
} }

回应:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

情况2:

{"query": {
"query_string" : {"default_field" : "name", "query" : "*Jan*"}
} }

回应:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}

情况3:

{"query": {
"query_string" : {"default_field" : "name", "query" : "*oh* *oe*"}
} }

回应:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

编辑 弹簧数据弹性搜索的相同实现 Https://stackoverflow.com/a/43579948/2357869

再解释一下 query _ string 如何比其他的更好 Https://stackoverflow.com/a/43321606/2357869

如果您想实现自动完成功能,那么 完成建议者是最简洁的解决方案。接下来的 博客文章包含了一个非常清晰的描述这是如何工作的。

简而言之,它是一种称为 FST 的内存数据结构,其中包含有效的建议,并为快速检索和内存使用进行了优化。本质上,它只是一个图表。例如,包含单词 hotelmarriotmercuremunchenmunich的 FST 看起来是这样的:

enter image description here

可以使用 regexp。

{ "_id" : "1", "name" : "John Doeman" , "function" : "Janitor"}
{ "_id" : "2", "name" : "Jane Doewoman","function" : "Teacher"  }
{ "_id" : "3", "name" : "Jimmy Jackal" ,"function" : "Student"  }

如果使用此查询:

{
"query": {
"regexp": {
"name": "J.*"
}
}
}

你会给所有的数据,他们的名字以“ J”开头。考虑到您希望只接收前两个以“ man”结尾的记录,因此您可以使用以下查询:

{
"query": {
"regexp": {
"name": ".*man"
}
}
}

如果您希望接收所有名称中存在“ m”的记录,可以使用以下查询:

{
"query": {
"regexp": {
"name": ".*m.*"
}
}
}

这对我有用。我希望我的答案能够适合解决你的问题。

使用通配符(*)可以防止计算分数

我正在用这个,我得到了我的工作

"query": {
"query_string" : {
"query" : "*test*",
"fields" : ["field1","field2"],
"analyze_wildcard" : true,
"allow_leading_wildcard": true
}
}

虽然有很多的答案集中在解决手头的问题,但是在选择一个特定的答案之前,并没有谈论很多人需要做出的各种权衡。因此,让我尝试添加一些关于这个视角的更多细节。

部分搜索现在是一个非常普遍和重要的功能,如果不正确实现可能会导致糟糕的用户体验和糟糕的性能 ,所以首先要知道你的应用程序的功能和非功能需求相关的这个功能,我在我的 这个详细的 SO 答案中谈到。

现在有各种各样的方法,比如查询时间、索引时间、完成建议程序和键入数据类型时的搜索在最新版本的 elasticssearch 中添加了。

现在,那些希望快速实现解决方案的人可以使用以下端到端工作解决方案。

索引映射

{
"settings": {
"analysis": {
"filter": {
"autocomplete_filter": {
"type": "ngram",
"min_gram": 1,
"max_gram": 10
}
},
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"autocomplete_filter"
]
}
}
},
"index.max_ngram_diff" : 10
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "standard"
}
}
}
}

给定样本文档的索引

{
"title" : "John Doeman"
  

}


{
"title" : "Jane Doewoman"
  

}


{
"title" : "Jimmy Jackal"
  

}

搜索查询

{
"query": {
"match": {
"title": "Doe"
}
}
}

它返回预期的搜索结果

 "hits": [
{
"_index": "6467067",
"_type": "_doc",
"_id": "1",
"_score": 0.76718915,
"_source": {
"title": "John Doeman"
}
},
{
"_index": "6467067",
"_type": "_doc",
"_id": "2",
"_score": 0.76718915,
"_source": {
"title": "Jane Doewoman"
}
}
]