小编典典

使用ElasticSearch模拟SQL LIKE搜索

elasticsearch

我只是从ElasticSearch开始,然后尝试基于它来实现自动完成功能。

我有一个autocomplete带有citytype 字段的索引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"足够了)。

这就是我的问题。我也希望它返回"Rio de Janeiro"。要获取"Rio de Janeiro",我需要发送以下查询:

  {  
       "query":{  
          "multi_match":{  
             "query":"Rio d",
             "analyzer":"standard",
             "fields":[  
                "city"
             ]
          }
       }
    }

注意"<whitespace>d"那里。

另一个相关问题是,我希望至少所有以开头的城市都"R"将通过以下查询返回:

  {  
       "query":{  
          "multi_match":{  
             "query":"R",
             "analyzer":"standard",
             "fields":[  
                "city"
             ]
          }
       }
    }

我期望"Rome",等等…(这是索引中存在的文档),但是,我只能"Rio"再次得到。我希望它的行为类似于SQL LIKE条件,即... LIKE 'CityName%'

我究竟做错了什么?


阅读 798

收藏
2020-06-22

共1个答案

小编典典

我会这样做:

  • 将令牌生成器更改为edge_nGram您曾说过的需要LIKE 'CityName%'(表示前缀匹配):
      "tokenizer": {
        "autocomplete_edge": {
          "type": "edge_nGram",
          "min_gram": 1,
          "max_gram": 100
        }
      }
  • 将字段指定autocomplete_searchsearch_analyzer。我认为拥有keywordand 是一个不错的选择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应该与上面的列表匹配。

为此,您希望输入文本小写并与用户设置的文本保持一致,这意味着不应对其进行分析。你为什么要这个?因为您已经用ngram分割了城市名称,并且不想为输入文本使用相同的名称。如果用户输入“
RI”,Elasticsearch将小写ri--并将其与索引中的内容完全匹配。

可能更快的替代方法multi_match是使用term,但这要求您的应用程序/网站将文本小写。这样做的原因是term根本不分析输入文本。

    {
      "query": {
        "filtered": {
          "filter": {
            "term": {
              "city": {
                "value": "ri"
              }
            }
          }
        }
      }
    }
2020-06-22