[Elasticsearch] 部分匹配 (一) - 前缀查询

部分匹配(Partial Matching)

敏锐的读者可能已经发现到目前为止,介绍的查询都是在整个词条层面进行操作的。匹配的最小单元必须是一个词条。你只能找到存在于倒排索引(Inverted Index)中的词条。

但是如果你想匹配词条的一部分,而不是整个词条呢?部分匹配(Partial Matching)允许用户指定词条的一部分然后找到含有该部分的任何单词。

匹配词条一部分这一需求在全文搜索引擎领域比你想象的要不那么常见。如果你有SQL的背景,你可能有过使用下面的SQL语句来实现一个简单的全文搜索功能的经历:

WHERE text LIKE "*quick*"
      AND text LIKE "*brown*"
      AND text LIKE "*fox*"

当然,通过ES我们可以借助分析过程(Analysis Process)和倒排索引来避免这种"蛮力"技术。为了同时匹配"fox"和"foxes",我们可以简单地使用一个词干提取器,然后将词干进行索引。这样就没有必要进行部分匹配了。

即便如此,在某些场合下部分匹配还是有作用的。常见的用例比如:

  • 匹配邮政编码,产品序列号,或者其它以某个特定前缀开头的或者能够匹配通配符甚至正则表达式的not_analyzed值。
  • 即时搜索(Search-as-you-type) - 在用户完成搜索词条的输入前就展示最有可能的结果。
  • 匹配德语或者荷兰语这一类语言,它们韩哟长复合单词,比如Weltgesundheitsorganisation(World Health Organization)。

我们以针对精确值not_analyzed字段的前缀匹配开始,介绍部分匹配的技术。

邮政编码和结构化数据

我们以英国的邮政编码来说明如何在结构化数据上使用部分匹配。英国邮政编码是一个定义清晰的结构。比如,W1V 3DG这个邮政编码可以被分解成以下几个部分:

  • W1V:这个部分表明了邮政地域和地区(Postal Area and District):

    • W 表明了地域(Area),使用一个或者两个字母。
    • 1V 表明了地区(District),使用一个或者两个数字,可能跟随一个字母。
  • 3DG:该部分表明了街道或者建筑:
    • 3 表明了区域(Sector),使用一个数字。
    • DG 表明了单元,使用两个字母。

假设我们将邮政编码索引为精确值的not_analyzed字段,因此我们可以创建如下索引:

PUT /my_index
{
    "mappings": {
        "address": {
            "properties": {
                "postcode": {
                    "type":  "string",
                    "index": "not_analyzed"
                }
            }
        }
    }
}

然后索引一些邮政编码:

PUT /my_index/address/1
{ "postcode": "W1V 3DG" }

PUT /my_index/address/2
{ "postcode": "W2F 8HW" }

PUT /my_index/address/3
{ "postcode": "W1F 7HW" }

PUT /my_index/address/4
{ "postcode": "WC1N 1LZ" }

PUT /my_index/address/5
{ "postcode": "SW5 0BE" }

现在我们的数据就准备就绪了。

前缀查询(Prefix Query)

我们可以通过一个简单的prefix查询来得到所有以W1开头的邮政编码:

GET /my_index/address/_search
{
    "query": {
        "prefix": {
            "postcode": "W1"
        }
    }
}

prefix查询是一个工作在词条级别的低级查询。它不会在搜索前对查询字符串进行解析。它假设用户会传入一个需要查询的精确前缀。

TIP

默认情况下,prefix查询不会计算相关度分值。它只是进行文档匹配,匹配的文档的分值为1。其实,相比查询它更像一个过滤器。prefix查询和prefix过滤器的唯一区别在于过滤器可以被缓存。

之前,我们提到过"你只能找到存在于倒排索引中的词条",但是对于这些邮政编码我们并没有进行任何特殊处理;每个邮政编码只是被当做精确值被简单地索引。那么prefix查询是如何工作的呢?

记住倒排索引是由唯一词条得有序列表构成的(此种情况下,即邮政编码)。对于每个词条,它会列举所有含有该词条的文档ID。对于我们的示例文档,倒排索引如下所示:

Term:          Doc IDs:
-------------------------
"SW5 0BE"    |  5
"W1F 7HW"    |  3
"W1V 3DG"    |  1
"W2F 8HW"    |  2
"WC1N 1LZ"   |  4
-------------------------

为了支持前缀匹配,查询会执行以下的步骤:

  1. 遍历词条列表并找到以W1开头的词条。
  2. 收集对应的文档ID。
  3. 移动到下一个词条。
  4. 如果该词条也以W1开头,那么重复步骤2;否则结束操作。

尽管以上的步骤对于我们的小例子而言能很好地工作,想象一下当倒排索引含有一百万个以W1开头的邮政编码时的情景,prefix查询需要访问一百万个词条来得到结果。

而前缀越短,就意味着需要访问越多的词条。如果我们查询前缀为W,而不是W1的词条,可能会匹配多达一千万个词条。

注意

prefix查询和过滤器对于即时(Ad-hoc)的前缀匹配是有用处的,但是在使用它们的时候需要小心。对于拥有少量词条的字段可以随意地使用,但是它们的扩展性较差,可能会让你的集群承受过多的压力。可以通过使用一个较长的前缀来限制它们对于集群的影响;这能够减少需要访问的词条的数量。

在本章的稍后部分,我们会介绍一种让前缀匹配更具效率的索引期间解决方案。但是首先,让我们看看两个相关的查询:wildcard以及regexp查询。

时间: 2024-08-06 15:37:32

[Elasticsearch] 部分匹配 (一) - 前缀查询的相关文章

[Elasticsearch] 部分匹配 (三) - 查询期间的即时搜索

本章翻译自Elasticsearch官方指南的Partial Matching一章. 查询期间的即时搜索(Query-time Search-as-you-type) 现在让我们来看看前缀匹配能够如何帮助全文搜索.用户已经习惯于在完成输入之前就看到搜索结果了 - 这被称为即时搜索(Instant Search, 或者Search-as-you-type).这不仅让用户能够在更短的时间内看到搜索结果,也能够引导他们得到真实存在于我们的索引中的结果. 比如,如果用户输入了johnnie walker

[Elasticsearch] 部分匹配 (二) - 通配符及正则表达式查询

通配符和正则表达式查询 wildcard查询和prefix查询类似,也是一个基于词条的低级别查询.但是它能够让你指定一个模式(Pattern),而不是一个前缀(Prefix).它使用标准的shell通配符:?用来匹配任意字符,*用来匹配零个或者多个字符. 以下查询能够匹配包含W1F 7HW和W2F 8HW的文档: GET /my_index/address/_search { "query": { "wildcard": { "postcode"

[Elasticsearch] 邻近匹配 (三) - 性能,关联单词查询以及Shingles

提高性能 短语和邻近度查询比简单的match查询在性能上更昂贵.match查询只是查看词条是否存在于倒排索引(Inverted Index)中,而match_phrase查询则需要计算和比较多个可能重复词条(Multiple possibly repeated)的位置. 在Lucene Nightly Benchmarks中,显示了一个简单的term查询比一个短语查询快大概10倍,比一个邻近度查询(一个拥有slop的短语查询)快大概20倍.当然,这个代价是在搜索期间而不是索引期间付出的. TIP

[Elasticsearch] 部分匹配 (四) - 索引期间优化ngrams及索引期间的即时搜索

本章翻译自Elasticsearch官方指南的Partial Matching一章. 索引期间的优化(Index-time Optimizations) 目前我们讨论的所有方案都是在查询期间的.它们不需要任何特殊的映射或者索引模式(Indexing Patterns):它们只是简单地工作在已经存在于索引中的数据之上. 查询期间的灵活性是有代价的:搜索性能.有时,将这些代价放到查询之外的地方是有价值的.在一个实时的Web应用中,一个额外的100毫秒的延迟会难以承受. 通过在索引期间准备你的数据,可

[ElasticSearch]Java API 之 词条查询(Term Level Query)

1. 词条查询(Term Query)  词条查询是ElasticSearch的一个简单查询.它仅匹配在给定字段中含有该词条的文档,而且是确切的.未经分析的词条.term 查询 会查找我们设定的准确值.term 查询本身很简单,它接受一个字段名和我们希望查找的值. 下面代码查询将匹配 college 字段中含有"California"一词的文档.记住,词条查询是未经分析的,因此需要提供跟索引文档中的词条完全匹配的词条.请注意,我们使用小写开头的california来搜索,而不是Cali

[Elasticsearch] 邻近匹配 (一) - 短语匹配以及slop參数

本文翻译自Elasticsearch官方指南的Proximity Matching一章. 邻近匹配(Proximity Matching) 使用了TF/IDF的标准全文搜索将文档,或者至少文档中的每一个字段,视作"一大袋的单词"(Big bag of Words).match查询可以告诉我们这个袋子中是否包括了我们的搜索词条,可是这仅仅是一个方面.它不能告诉我们关于单词间关系的不论什么信息. 考虑下面这些句子的差别: Sue ate the alligator. The alligat

[Elasticsearch] 控制相关度 (五) - function_score查询及field_value_factor,boost_mode,max_mode参数

本章翻译自Elasticsearch官方指南的Controlling Relevance一章. function_score查询 function_score查询是处理分值计算过程的终极工具.它让你能够对所有匹配了主查询的每份文档调用一个函数来调整甚至是完全替换原来的_score. 实际上,你可以通过设置过滤器来将查询得到的结果分成若干个子集,然后对每个子集使用不同的函数.这样你就能够同时得益于:高效的分值计算以及可缓存的过滤器. 它拥有几种预先定义好了的函数: weight 对每份文档适用一个

[Elasticsearch] 控制相关度 (三) - 通过查询结构调整相关度以及boosting查询

本章翻译自Elasticsearch官方指南的Controlling Relevance一章. 通过查询结构调整相关度 ES提供的查询DSL是相当灵活的.你可以通过将单独的查询子句在查询层次中上下移动来让它更重要/更不重要.比如,下面的查询: quick OR brown OR red OR fox 我们可以使用一个bool查询,对所有词条一视同仁: GET /_search { "query": { "bool": { "should": [

[Elasticsearch] 邻近匹配 (一) - 短语匹配以及slop参数

本文翻译自Elasticsearch官方指南的Proximity Matching一章. 邻近匹配(Proximity Matching) 使用了TF/IDF的标准全文搜索将文档,或者至少文档中的每个字段,视作"一大袋的单词"(Big bag of Words).match查询能够告诉我们这个袋子中是否包含了我们的搜索词条,但是这只是一个方面.它不能告诉我们关于单词间关系的任何信息. 考虑以下这些句子的区别: Sue ate the alligator. The alligator a