使用ElasticSearch模拟SQL的LIKE搜索

5

我刚开始接触ElasticSearch,并尝试基于它实现一个自动完成功能。

我有一个名为autocomplete的索引,其中包含一个类型为stringcity字段。以下是存储在此索引中的文档示例:

{  
   "_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"
               ]
            }
         }
      ]
   }
}

就大部分而言,它是有效的。在用户实际输入完整单词之前("Ri"足够了),它会找到具有city = "Rio"的文档。

但问题出在这里。我也想返回"Rio de Janeiro"。为了得到"Rio de Janeiro",我需要发送以下查询:

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

请注意那里的"d"。
另一个相关问题是,我期望至少所有以R开头的城市都能在以下查询中返回:
  {  
       "query":{  
          "multi_match":{  
             "query":"R",
             "analyzer":"standard",
             "fields":[  
                "city"
             ]
          }
       }
    }

我期望返回的是存在于索引中的文档"Rome"等城市名,但是我只得到了"Rio"。我希望它像SQL LIKE条件一样运行,即... LIKE 'CityName%'

我做错了什么?

2个回答

2
我会这样做:
  • 将分词器更改为edge_nGram,因为您说您需要LIKE 'CityName%'(意思是前缀匹配):
  "tokenizer": {
    "autocomplete_edge": {
      "type": "edge_nGram",
      "min_gram": 1,
      "max_gram": 100
    }
  }
  • 将字段指定为autocomplete_search作为search_analyzer。我认为使用keywordlowercase很不错:
  "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" 并与索引中的内容精确匹配。
使用 term 可能比 multi_match 更快,但这需要你的应用程序/网站将文本转换为小写。原因是 term 根本不分析输入文本。
{
  "query": {
    "filtered": {
      "filter": {
        "term": {
          "city": {
            "value": "ri"
          }
        }
      }
    }
  }
}

1

嗨,我采用了Andrei描述的方法,但是知道Completion Suggester也很棒。谢谢! - FullOfCaffeine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接