Elasticsearch-精确查找

Elasticsearch-精确查找

转译:(https://www.elastic.co/guide/en/elasticsearch/guide/current/_finding_exact_values.html#_finding_exact_values

当进行精确值查找时, 我们会使用过滤器(filters)。过滤器很重要,因为它们执行速度非常快,不会计算相关度(直接跳过了整个评分阶段)而且很容易被缓存。不过现在只要记住:请尽可能多的使用过滤式查询。

term 查询数字编辑

我们首先来看最为常用的 term 查询, 可以用它处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。

让我们以下面的例子开始介绍,创建并索引一些表示产品的文档,文档里有字段 `price` 和 `productID` ( `价格` 和 `产品ID` ):
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

 

我们想要做的是查找具有某个价格的所有产品,有关系数据库背景的人肯定熟悉 SQL,如果我们将其用 SQL 形式表达,会是下面这样:

SELECT document
FROM   products
WHERE  price = 20

在 Elasticsearch 的查询表达式(query DSL)中,我们可以使用 term 查询达到相同的目的。 term 查询会查找我们指定的精确值。作为其本身, term 查询是简单的。它接受一个字段名以及我们希望查找的数值:

{
    "term" : {
        "price" : 20
    }
}

通常当查找一个精确值的时候,我们不希望对查询进行评分计算。只希望对文档进行包括或排除的计算,所以我们会使用 constant_score 查询以非评分模式来执行 term 查询并以一作为统一评分。

最终组合的结果是一个 constant_score 查询,它包含一个 term 查询:

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : { 

<span>Elasticsearch-精确查找</span>

            "filter" : {
                "term" : { 

<span>Elasticsearch-精确查找</span>

                    "price" : 20
                }
            }
        }
    }
}

 

<span>Elasticsearch-精确查找</span>

我们用 constant_scoreterm 查询转化成为过滤器

<span>Elasticsearch-精确查找</span>

我们之前看到过的 term 查询

执行后,这个查询所搜索到的结果与我们期望的一致:只有文档 2 命中并作为结果返回(因为只有 2 的价格是 20 ):

"hits" : [
    {
        "_index" : "my_store",
        "_type" :  "products",
        "_id" :    "2",
        "_score" : 1.0, 

<span>Elasticsearch-精确查找</span>

        "_source" : {
          "price" :     20,
          "productID" : "KDKE-B-9947-#kL5"
        }
    }
]

<span>Elasticsearch-精确查找</span>

查询置于 filter 语句内不进行评分或相关度的计算,所以所有的结果都会返回一个默认评分 1

term 查询文本编辑

如本部分开始处提到过的一样 ,使用 term 查询匹配字符串和匹配数字一样容易。如果我们想要查询某个具体 UPC ID 的产品,使用 SQL 表达式会是如下这样:

SELECT product
FROM   products
WHERE  productID = "XHDK-A-1293-#fJ3"

 

转换成查询表达式(query DSL),同样使用 term 查询,形式如下:

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

 

但这里有个小问题:我们无法获得期望的结果。为什么呢?问题不在 term 查询,而在于索引数据的方式。 如果我们使用 analyze API (分析 API),我们可以看到这里的 UPC 码被拆分成多个更小的 token :

GET /my_store/_analyze
{
  "field": "productID",
  "text": "XHDK-A-1293-#fJ3"
}

如下:

{
  "tokens" : [ {
    "token" :        "xhdk",
    "start_offset" : 0,
    "end_offset" :   4,
    "type" :         "<ALPHANUM>",
    "position" :     1
  }, {
    "token" :        "a",
    "start_offset" : 5,
    "end_offset" :   6,
    "type" :         "<ALPHANUM>",
    "position" :     2
  }, {
    "token" :        "1293",
    "start_offset" : 7,
    "end_offset" :   11,
    "type" :         "<NUM>",
    "position" :     3
  }, {
    "token" :        "fj3",
    "start_offset" : 13,
    "end_offset" :   16,
    "type" :         "<ALPHANUM>",
    "position" :     4
  } ]
}

这里有几点需要注意:

  • Elasticsearch 用 4 个不同的 token 而不是单个 token 来表示这个 UPC 。
  • 所有字母都是小写的。
  • 丢失了连字符和哈希符( # )。

所以当我们用 term 查询查找精确值 XHDK-A-1293-#fJ3 的时候,找不到任何文档,因为它并不在我们的倒排索引中,正如前面呈现出的分析结果,索引里有四个 token 。

显然这种对 ID 码或其他任何精确值的处理方式并不是我们想要的。

为了避免这种问题,我们需要告诉 Elasticsearch 该字段具有精确值,要将其设置成 not_analyzed 无需分析的。 我们可以在 自定义字段映射 中查看它的用法。为了修正搜索结果,我们需要首先删除旧索引(因为它的映射不再正确)然后创建一个能正确映射的新索引:

DELETE /my_store 

<span>Elasticsearch-精确查找</span>

PUT /my_store 

<span>Elasticsearch-精确查找</span>

{
    "mappings" : {
        "products" : {
            "properties" : {
                "productID" : {
                    "type" : "string",
                    "index" : "not_analyzed" 

<span>Elasticsearch-精确查找</span>

                }
            }
        }
    }

}

执行顺序说明:

<span>Elasticsearch-精确查找</span>

删除索引是必须的,因为我们不能更新已存在的映射。

<span>Elasticsearch-精确查找</span>

在索引被删除后,我们可以创建新的索引并为其指定自定义映射。

<span>Elasticsearch-精确查找</span>

这里我们告诉 Elasticsearch ,我们不想对 productID 做任何分析。

 

 

 

 

 

现在我们可以为文档重建索引:

POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

 

此时, term 查询就能搜索到我们想要的结果,让我们再次搜索新索引过的数据(注意,查询和过滤并没有发生任何改变,改变的是数据映射的方式):

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

 

因为 productID 字段是未分析过的, term 查询不会对其做任何分析,查询会进行精确查找并返回文档 1 。成功!

内部过滤器的操作编辑

在内部,Elasticsearch 会在运行非评分查询的时执行多个操作:

  1. 查找匹配文档.

    term 查询在倒排索引中查找 XHDK-A-1293-#fJ3 然后获取包含该 term 的所有文档。本例中,只有文档 1 满足我们要求。

  2. 创建 bitset.

    过滤器会创建一个 bitset (一个包含 0 和 1 的数组),它描述了哪个文档会包含该 term 。匹配文档的标志位是 1 。本例中,bitset 的值为 [1,0,0,0] 。在内部,它表示成一个 “roaring bitmap”,可以同时对稀疏或密集的集合进行高效编码。

  3. 迭代 bitset(s)

    一旦为每个查询生成了 bitsets ,Elasticsearch 就会循环迭代 bitsets 从而找到满足所有过滤条件的匹配文档的集合。执行顺序是启发式的,但一般来说先迭代稀疏的 bitset (因为它可以排除掉大量的文档)。

  4. 增量使用计数.

    Elasticsearch 能够缓存非评分查询从而获取更快的访问,但是它也会不太聪明地缓存一些使用极少的东西。非评分计算因为倒排索引已经足够快了,所以我们只想缓存那些我们 知道 在将来会被再次使用的查询,以避免资源的浪费。

    为了实现以上设想,Elasticsearch 会为每个索引跟踪保留查询使用的历史状态。如果查询在最近的 256 次查询中会被用到,那么它就会被缓存到内存中。当 bitset 被缓存后,缓存会在那些低于 10,000 个文档(或少于 3% 的总索引数)的段(segment)中被忽略。这些小的段即将会消失,所以为它们分配缓存是一种浪费。

实际情况并非如此(执行有它的复杂性,这取决于查询计划是如何重新规划的,有些启发式的算法是基于查询代价的),理论上非评分查询 先于 评分查询执行。非评分查询任务旨在降低那些将对评分查询计算带来更高成本的文档数量,从而达到快速搜索的目的。

从概念上记住非评分计算是首先执行的,这将有助于写出高效又快速的搜索请求。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/119684.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Collection家族成员

    Collection家族成员

    2021年9月15日
    83
  • C++ map遍历(简单易记忆)[通俗易懂]

    C++ map遍历(简单易记忆)[通俗易懂]C++中map遍历有两种方法:第一种,使用迭代器,while循环#include&lt;iostream&gt;#include&lt;map&gt;usingnamespacestd;intmain(){ map&lt;int,int&gt;p; p[0]=1; p[1]=2; p[3]=4; map&lt;int,int&gt;::iteratori…

    2022年9月21日
    0
  • SpringBootTest使用MockMvc测试Controller

    SpringBootTest使用MockMvc测试Controllerimportstaticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;importstaticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringRunner.class)@…

    2022年5月5日
    45
  • 区块链 | 流行的分布式存储平台Swarm和IPFS分析「建议收藏」

    区块链 | 流行的分布式存储平台Swarm和IPFS分析「建议收藏」谈到区块链和分布式计算,最重要的是共识算法(整个网络如何达成一致的决策)和智能合约(实现我们在中心化世界中日常使用的应用)。但是,谈及日常应用程序,这些属性还不足以支持当今世界的需求。如果我们只依靠上述这两项,就难以想象像在Netflix上那样观赏喜爱的电影或电视剧、像在脸书上那样存储或分享值得纪念的视频或照片,或在区块链上玩喜爱的在线游戏(如DOTA)。我们缺少一个强大、安全和去中心化的内容存储以及分发系统,服务于当今的应用程序。下面,我们将探索和评估一些***的分布式存储平台。本文为系

    2022年5月16日
    47
  • 几种简单的随机数算法「建议收藏」

    几种简单的随机数算法「建议收藏」(1)产生一个范围内的随机数j=(int)(100.0*rand()/(RAND_MAX+1.0))//产生0到100的随机数(2)筛选型随机数x=random(100);while(x==6){x=random(100);}//产生0-99的随机数但不能是6(3)从一段连续的范围内取随机数x=random(11)+

    2022年7月26日
    43
  • Python学习手册之正则表达式示例–邮箱地址提取

    Python学习手册之正则表达式示例–邮箱地址提取Python学习手册之正则表达式示例–邮箱地址提取

    2022年4月21日
    27

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号