使用 ElasticSearch 模拟 SQL LIKE 搜索
Emulate a SQL LIKE search with ElasticSearch
我刚开始使用 ElasticSearch 并尝试基于它实现自动完成功能。
我有一个 autocomplete
索引,其中的字段 city
类型为 string
。这是存储到该索引中的文档示例:
{
"_index":"autocomplete_1435797593949",
"_type":"listing",
"_id":"40716",
"_source":{
"city":"Rome",
"tags":[
"listings"
]
}
}
分析配置如下所示:
{
"analyzer":{
"autocomplete_term":{
"tokenizer":"autocomplete_edge",
"filter":[
"lowercase"
]
},
"autocomplete_search":{
"tokenizer":"keyword",
"filter":[
"lowercase"
]
}
},
"tokenizer":{
"autocomplete_edge":{
"type":"nGram",
"min_gram":1,
"max_gram":100
}
}
}
映射:
{
"autocomplete_1435795884170":{
"mappings":{
"listing":{
"properties":{
"city":{
"type":"string",
"analyzer":"autocomplete_term"
},
}
}
}
}
}
我正在向 ES 发送以下查询:
{
"query":{
"multi_match":{
"query":"Rio",
"analyzer":"autocomplete_search",
"fields":[
"city"
]
}
}
}
结果,我得到以下信息:
{
"took":2,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"failed":0
},
"hits":{
"total":1,
"max_score":2.7742395,
"hits":[
{
"_index":"autocomplete_1435795884170",
"_type":"listing",
"_id":"53581",
"_score":2.7742395,
"_source":{
"city":"Rio",
"tags":[
"listings"
]
}
}
]
}
}
在大多数情况下,它是有效的。它确实在用户必须实际键入整个单词之前找到带有 city = "Rio"
的文档("Ri"
就足够了)。
这就是我的问题。我也想要 return "Rio de Janeiro"
。要获得 "Rio de Janeiro"
,我需要发送以下查询:
{
"query":{
"multi_match":{
"query":"Rio d",
"analyzer":"standard",
"fields":[
"city"
]
}
}
}
注意那里的 "<whitespace>d"
。
另一个相关的问题是,我希望至少所有以 "R"
开头的城市都使用以下查询 returned:
{
"query":{
"multi_match":{
"query":"R",
"analyzer":"standard",
"fields":[
"city"
]
}
}
}
我希望 "Rome"
,等等...(这是索引中存在的文档),但是,我再次只得到 "Rio"
。我希望它表现得像 SQL LIKE
条件,即 ... LIKE 'CityName%'
.
我做错了什么?
Elasticsearch
里有Completion Suggester
提建议。 Completion Suggester
我会这样做:
- 将分词器更改为
edge_nGram
,因为您说您需要 LIKE 'CityName%'
(表示前缀匹配):
"tokenizer": {
"autocomplete_edge": {
"type": "edge_nGram",
"min_gram": 1,
"max_gram": 100
}
}
- 让字段将您的
autocomplete_search
指定为 search_analyzer
。我认为 keyword
和 lowercase
: 是个不错的选择
"mappings": {
"listing": {
"properties": {
"city": {
"type": "string",
"index_analyzer": "autocomplete_term",
"search_analyzer": "autocomplete_search"
}
}
}
}
- 查询本身很简单:
{
"query": {
"multi_match": {
"query": "R",
"fields": [
"city"
]
}
}
}
详细解释如下:将您的城市名称拆分为边 ngram。例如,对于 Rio de Janeiro
,您将索引如下内容:
"city": [
"r",
"ri",
"rio",
"rio ",
"rio d",
"rio de",
"rio de ",
"rio de j",
"rio de ja",
"rio de jan",
"rio de jane",
"rio de janei",
"rio de janeir",
"rio de janeiro"
]
您注意到所有内容都是小写的。现在,您希望查询获取任何文本(小写或非小写)并将其与索引中的内容相匹配。因此,R
应该与上面的列表匹配。
为此,您需要将输入文本小写并保持与用户设置的完全一样,这意味着不应对其进行分析。你为什么要这个?因为您已经在 ngrams 中拆分了城市名称,并且您不希望输入文本使用相同的名称。如果用户输入 "RI",Elasticsearch 会将其小写 - ri
- 并将其与索引中的内容完全匹配。
可能比 multi_match
更快的替代方法是使用 term
,但这需要您的 application/website 将文本小写。原因是 term
根本不分析输入文本。
{
"query": {
"filtered": {
"filter": {
"term": {
"city": {
"value": "ri"
}
}
}
}
}
}
我刚开始使用 ElasticSearch 并尝试基于它实现自动完成功能。
我有一个 autocomplete
索引,其中的字段 city
类型为 string
。这是存储到该索引中的文档示例:
{
"_index":"autocomplete_1435797593949",
"_type":"listing",
"_id":"40716",
"_source":{
"city":"Rome",
"tags":[
"listings"
]
}
}
分析配置如下所示:
{
"analyzer":{
"autocomplete_term":{
"tokenizer":"autocomplete_edge",
"filter":[
"lowercase"
]
},
"autocomplete_search":{
"tokenizer":"keyword",
"filter":[
"lowercase"
]
}
},
"tokenizer":{
"autocomplete_edge":{
"type":"nGram",
"min_gram":1,
"max_gram":100
}
}
}
映射:
{
"autocomplete_1435795884170":{
"mappings":{
"listing":{
"properties":{
"city":{
"type":"string",
"analyzer":"autocomplete_term"
},
}
}
}
}
}
我正在向 ES 发送以下查询:
{
"query":{
"multi_match":{
"query":"Rio",
"analyzer":"autocomplete_search",
"fields":[
"city"
]
}
}
}
结果,我得到以下信息:
{
"took":2,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"failed":0
},
"hits":{
"total":1,
"max_score":2.7742395,
"hits":[
{
"_index":"autocomplete_1435795884170",
"_type":"listing",
"_id":"53581",
"_score":2.7742395,
"_source":{
"city":"Rio",
"tags":[
"listings"
]
}
}
]
}
}
在大多数情况下,它是有效的。它确实在用户必须实际键入整个单词之前找到带有 city = "Rio"
的文档("Ri"
就足够了)。
这就是我的问题。我也想要 return "Rio de Janeiro"
。要获得 "Rio de Janeiro"
,我需要发送以下查询:
{
"query":{
"multi_match":{
"query":"Rio d",
"analyzer":"standard",
"fields":[
"city"
]
}
}
}
注意那里的 "<whitespace>d"
。
另一个相关的问题是,我希望至少所有以 "R"
开头的城市都使用以下查询 returned:
{
"query":{
"multi_match":{
"query":"R",
"analyzer":"standard",
"fields":[
"city"
]
}
}
}
我希望 "Rome"
,等等...(这是索引中存在的文档),但是,我再次只得到 "Rio"
。我希望它表现得像 SQL LIKE
条件,即 ... LIKE 'CityName%'
.
我做错了什么?
Elasticsearch
里有Completion Suggester
提建议。 Completion Suggester
我会这样做:
- 将分词器更改为
edge_nGram
,因为您说您需要LIKE 'CityName%'
(表示前缀匹配):
"tokenizer": {
"autocomplete_edge": {
"type": "edge_nGram",
"min_gram": 1,
"max_gram": 100
}
}
- 让字段将您的
autocomplete_search
指定为search_analyzer
。我认为keyword
和lowercase
: 是个不错的选择
"mappings": {
"listing": {
"properties": {
"city": {
"type": "string",
"index_analyzer": "autocomplete_term",
"search_analyzer": "autocomplete_search"
}
}
}
}
- 查询本身很简单:
{
"query": {
"multi_match": {
"query": "R",
"fields": [
"city"
]
}
}
}
详细解释如下:将您的城市名称拆分为边 ngram。例如,对于 Rio de Janeiro
,您将索引如下内容:
"city": [
"r",
"ri",
"rio",
"rio ",
"rio d",
"rio de",
"rio de ",
"rio de j",
"rio de ja",
"rio de jan",
"rio de jane",
"rio de janei",
"rio de janeir",
"rio de janeiro"
]
您注意到所有内容都是小写的。现在,您希望查询获取任何文本(小写或非小写)并将其与索引中的内容相匹配。因此,R
应该与上面的列表匹配。
为此,您需要将输入文本小写并保持与用户设置的完全一样,这意味着不应对其进行分析。你为什么要这个?因为您已经在 ngrams 中拆分了城市名称,并且您不希望输入文本使用相同的名称。如果用户输入 "RI",Elasticsearch 会将其小写 - ri
- 并将其与索引中的内容完全匹配。
可能比 multi_match
更快的替代方法是使用 term
,但这需要您的 application/website 将文本小写。原因是 term
根本不分析输入文本。
{
"query": {
"filtered": {
"filter": {
"term": {
"city": {
"value": "ri"
}
}
}
}
}
}