• 查找准确值
    • 用于数字的 term 过滤器
    • 用于文本的 term 过滤器
    • 内部过滤操作

    查找准确值

    对于准确值,你需要使用过滤器。过滤器的重要性在于它们非常的快。它们不计算相关性(避过所有计分阶段)而且很容易被缓存。我们今后再来讨论过滤器的性能优势【过滤器缓存】,现在,请先记住尽可能多的使用过滤器。

    用于数字的 term 过滤器

    我们下面将介绍 term 过滤器,首先因为你可能经常会用到它,这个过滤器旨在处理数字,布尔值,日期,和文本。

    我们来看一下例子,一些产品最初用数字来索引,包含两个字段 priceproductID

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

    我们的目标是找出特定价格的产品。假如你有关系型数据库背景,可能用 SQL 来表现这次查询比较熟悉,它看起来像这样:

    1. SELECT document
    2. FROM products
    3. WHERE price = 20

    在 Elasticsearch DSL 中,我们使用 term 过滤器来实现同样的事。term 过滤器会查找我们设定的准确值。term 过滤器本身很简单,它接受一个字段名和我们希望查找的值:

    1. {
    2. "term" : {
    3. "price" : 20
    4. }
    5. }

    term 过滤器本身并不能起作用。像在【查询 DSL】中介绍的一样,搜索 API 需要得到一个查询语句,而不是一个 过滤器。为了使用 term 过滤器,我们需要将它包含在一个过滤查询语句中:

    1. GET /my_store/products/_search
    2. {
    3. "query" : {
    4. "filtered" : { <1>
    5. "query" : {
    6. "match_all" : {} <2>
    7. },
    8. "filter" : {
    9. "term" : { <3>
    10. "price" : 20
    11. }
    12. }
    13. }
    14. }
    15. }

    <1> filtered 查询同时接受 queryfilter

    <2> match_all 用来匹配所有文档,这是默认行为,所以在以后的例子中我们将省略掉 query 部分。

    <3> 这是我们上面见过的 term 过滤器。注意它在 filter 分句中的位置。

    执行之后,你将得到预期的搜索结果:只能文档 2 被返回了(因为只有 2 的价格是 20):

    1. "hits" : [
    2. {
    3. "_index" : "my_store",
    4. "_type" : "products",
    5. "_id" : "2",
    6. "_score" : 1.0, <1>
    7. "_source" : {
    8. "price" : 20,
    9. "productID" : "KDKE-B-9947-#kL5"
    10. }
    11. }
    12. ]

    <1> 过滤器不会执行计分和计算相关性。分值由 match_all 查询产生,所有文档一视同仁,所有每个结果的分值都是 1

    用于文本的 term 过滤器

    像我们在开头提到的,term 过滤器可以像匹配数字一样轻松的匹配字符串。让我们通过特定 UPC 标识码来找出产品,而不是通过价格。如果用 SQL 来实现,我们可能会使用下面的查询:

    1. SELECT product
    2. FROM products
    3. WHERE productID = "XHDK-A-1293-#fJ3"

    转到查询 DSL,我们用 term 过滤器来构造一个类似的查询:

    1. GET /my_store/products/_search
    2. {
    3. "query" : {
    4. "filtered" : {
    5. "filter" : {
    6. "term" : {
    7. "productID" : "XHDK-A-1293-#fJ3"
    8. }
    9. }
    10. }
    11. }
    12. }

    有点出乎意料:我们没有得到任何结果值!为什么呢?问题不在于 term 查询;而在于数据被索引的方式。如果我们使用 analyze API,我们可以看到 UPC 被分解成短小的表征:

    1. GET /my_store/_analyze?field=productID
    2. XHDK-A-1293-#fJ3
    1. {
    2. "tokens" : [ {
    3. "token" : "xhdk",
    4. "start_offset" : 0,
    5. "end_offset" : 4,
    6. "type" : "<ALPHANUM>",
    7. "position" : 1
    8. }, {
    9. "token" : "a",
    10. "start_offset" : 5,
    11. "end_offset" : 6,
    12. "type" : "<ALPHANUM>",
    13. "position" : 2
    14. }, {
    15. "token" : "1293",
    16. "start_offset" : 7,
    17. "end_offset" : 11,
    18. "type" : "<NUM>",
    19. "position" : 3
    20. }, {
    21. "token" : "fj3",
    22. "start_offset" : 13,
    23. "end_offset" : 16,
    24. "type" : "<ALPHANUM>",
    25. "position" : 4
    26. } ]
    27. }

    这里有一些要点:

    • 我们得到了四个分开的标记,而不是一个完整的标记来表示 UPC。
    • 所有的字符都被转为了小写。
    • 我们失去了连字符和 # 符号。

    所以当我们用 XHDK-A-1293-#fJ3 来查找时,得不到任何结果,因为这个标记不在我们的倒排索引中。相反,那里有上面列出的四个标记。

    显然,在处理唯一标识码,或其他枚举值时,这不是我们想要的结果。

    为了避免这种情况发生,我们需要通过设置这个字段为 not_analyzed 来告诉 Elasticsearch 它包含一个准确值。我们曾在【自定义字段映射】中见过它。为了实现目标,我们要先删除旧索引(因为它包含了错误的映射),并创建一个正确映射的索引:

    1. DELETE /my_store <1>
    2. PUT /my_store <2>
    3. {
    4. "mappings" : {
    5. "products" : {
    6. "properties" : {
    7. "productID" : {
    8. "type" : "string",
    9. "index" : "not_analyzed" <3>
    10. }
    11. }
    12. }
    13. }
    14. }

    <1> 必须首先删除索引,因为我们不能修改已经存在的映射。

    <2> 删除后,我们可以用自定义的映射来创建它。

    <3> 这里我们明确表示不希望 productID 被分析。

    现在我们可以继续重新索引文档:

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

    现在我们的 term 过滤器将按预期工作。让我们在新索引的数据上再试一次(注意,查询和过滤都没有修改,只是数据被重新映射了)。

    1. GET /my_store/products/_search
    2. {
    3. "query" : {
    4. "filtered" : {
    5. "filter" : {
    6. "term" : {
    7. "productID" : "XHDK-A-1293-#fJ3"
    8. }
    9. }
    10. }
    11. }
    12. }

    productID 字段没有经过分析,term 过滤器也没有执行分析,所以这条查询找到了准确匹配的值,如期返回了文档 1。

    内部过滤操作

    Elasticsearch 在内部会通过一些操作来执行一次过滤:

    1. 查找匹配文档

      term 过滤器在倒排索引中查找词 XHDK-A-1293-#fJ3,然后返回包含那个词的文档列表。在这个例子中,只有文档 1 有我们想要的词。

    2. 创建字节集

      然后过滤器将创建一个 字节集 —— 一个由 1 和 0 组成的数组 —— 描述哪些文档包含这个词。匹配的文档得到 1 字节,在我们的例子中,字节集将是 [1,0,0,0]

    3. 缓存字节集

      最后,字节集被储存在内存中,以使我们能用它来跳过步骤 1 和 2。这大大的提升了性能,让过滤变得非常的快。

    当执行 filtered 查询时,filter 会比 query 早执行。结果字节集会被传给 query 来跳过已经被排除的文档。这种过滤器提升性能的方式,查询更少的文档意味着更快的速度。