# 查询

本文记录查询 ES document 的相关知识。

# 简单查询

  • ES 支持通过 GET 请求进行简单查询。

    • 相关 API :
      GET /<index>/_doc/_search   # 查询指定索引下的文档
      GET /<index>/_search        # 可以省略 _doc/
      GET /_search                # 查询所有索引下的文档
      
      GET /_search?q=name:Leo     # 可以在 URL 的查询字符串中加入 q 字段及查询参数
      
  • 对 ES 发出查询请求之后,收到的响应内容格式如下:

    [[email protected] ~]# curl -X GET 127.0.0.1:9200/student/_search?pretty
    {
      "took" : 14,                  # 本次查询的耗时,单位为 ms
      "timed_out" : false,          # 该请求的处理过程是否超时
      "_shards" : {
        "total" : 1,                # 总共查询了多少个分片
        "successful" : 1,           # 有多少个分片成功了
        "skipped" : 0,              # 有多少个分片跳过了
        "failed" : 0                # 有多少个分片失败了
      },
      "hits" : {
        "total" : {
          "value" : 8,              # 查询条件匹配的文档数量(但实际返回的文档数可能受限制,更小)
          "relation" : "eq"         # 这些文档与查询条件的关系
        },
        "max_score" : 1.0,          # 这些文档的最大相关性
        "hits" : [                  # hits 字典中的 hits 列表包含了 ES 实际返回给客户端的文档
          {
            "_index" : "student",
            "_type" : "_doc",
            "_id" : "ZhzMiXABzduhqPWX7mUX",
            "_score" : 1.0,         # 一个正浮点数,表示该文档与查询条件的相关性
            "_source" : {
              "name" : "Leo",
              "age" : 10,
              "interests" : [
                "sports",
                "music"
              ]
            }
          },
          ...
    
    • ES 默认将 hits 列表中的各个文档按 _score 的值进行排序。

# DSL 查询

  • ES 提供了一种 DSL 查询语法,可以实现复杂的查询。

    • 它需要在请求报文 body 中加入 JSON 格式的查询参数。
    • 相关 API :
      GET  /<index>/_search           [request_body]
      GET  /_search                   [request_body]
      
      POST /<index>/_delete_by_query  [request_body]    # 删除与 DSL 查询条件匹配的文档
                    ?proceed=proceed                    # 如果遇到文档 version conflict ,默认动作为 abort ,建议改为 proceed
                    ?scroll_size=1000                   # 每个 batch 大小,默认为 1000 。增加该值可以些许提高删除速度
                    ?wait_for_completion=false          # 如果操作耗时过久,建议改为后台执行的 task
      POST /<index>/_update_by_query  [request_body]    # 修改与 DSL 查询条件匹配的文档
      
      • 可以使用 GET 或 POST 方法。
      • 如果省略 request_body ,则会匹配所有文档,相当于简单查询。
  • 例:查询并修改文档

    POST /my-index-1/_update_by_query
    {
      "query": {
        "wildcard": {
          "ip":{
            "value": "10.0.0.*"
          }
        }
      },
      "script": {
        "source": "ctx._source['hostname'] = 'CentOS'; ctx._source['release'] = '7'"
      }
    }
    

# match

:用于根据字段的值查询文档。

  • 例:
    GET /student/_search
    {
      "query" : {
        "match" : {
          "name" : {            # 指定查询的目标字段
            "query": "Leo"      # 指定查询字符串,找出目标字段包含 query_string 的文档
            # "operator": "or", # 设置当 query_string 被分词之后,几个词之间的逻辑关系。可以取值为 or 或 and
            # "fuzziness": 0,
            # "max_expansions": 50,
          }
        }
      },
      # "_source": {            # 让返回的文档中只包含指定的字段
      #   "includes": [
      #     "filed1",
      #     "filed2"
      #   ],
      #   "excludes": [
      #     "filed3"
      #   ]
      # }
    }
    
    • 没有其它查询参数时,可以简写为:
      "match" : {
        "name" : "Leo"
      }
      
    • 查询的字段可以是以下形式:
      "age" : 10                        # 字段的值可以是 string、number、boolean 等类型
      "age" : "10"                      # number 类型的值也可以写作 string 形式
      
      "host.os.platform" : "centos"     # 字段名可以通过 . 引用子字段
      
  • 常见的几种 match 子句:
    "match"        : { "name" : "A B" } # 分词之后,查询存在这些词语之一的文档。这里是查询 name 包含 A 或 B 的文档
    
    "match_phrase" : { "name" : "A B" } # 分词之后,查询同时存在这些词语的文档,且词语的先后顺序一致
    
    "match_all"    : { }                # 匹配所有文档
    
    "multi_match"  : {
      "fields": [ "name", "nickname", "*_name" ]    # 指定多个字段进行 match 查询
      "query": "Leo",
    }
    
  • 如果 query_string 包含保留字符 + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ / ,则需要用 \ 转义。采用 JSON 格式时需要用 \\ 转义。
    • < > 总数不支持转义。

# term

:用于进行精确查询。

  • 例:
    GET /_search
    {
      "query": {
        "term": {
          "name": {
            "value": "Leo"    # 字段值必须等于它
            # "boost": 1.0,   # 控制相关性分数 _score 。默认取值为 1.0 ,取值小于 1 会降低得分,取值大于 1 会增加得分
          }
        }
      }
    }
    

# exists

:用于查询存在指定字段的文档。

  • 例:
    GET /_search
    {
      "query": {
        "exists": {
          "field": "name"
        }
      }
    }
    

# range

:用于查询字段值在指定范围内的文档。

  • 例:
    GET /_search
    {
      "range" : {
        "age" : {
          "gt" : 10,        # 大于
          "lt" : 20,        # 小于
          # "gte" : 20,     # 大于等于
          # "lte" : 20,     # 小于等于
        }
      }
    }
    

# prefix

:用于进行前缀查询。

  • 只能查询字符串类型的字段。
  • 例:
    GET /_search
    {
      "query": {
        "prefix": {
          "name": {
            "value": "Leo"  # 字段值需要以该前缀开头
          }
        }
      }
    }
    

# wildcard

:用于进行通配符查询。

  • 可以使用通配符 ? 和 * 。
  • 例:
    GET /_search
    {
      "query": {
        "wildcard": {
          "name": {
            "value": "Leo*"   # 字段值需要匹配该 pattern
          }
        }
      }
    }
    
    • 用于查询的 pattern 应该避免以通配符开头,否则会大幅增加查询的开销。

# regexp

:用于进行正则查询。

  • 例:
    GET /_search
    {
      "query": {
        "regexp": {
          "name": {
            "value": "Leo.*"
          }
        }
      }
    }
    
    • text 类型的字段在存储时会被分词,因此需要某个词语匹配正则表达式。

# fuzzy

:用于进行模糊查询。

  • 模糊查询时,会将查询的值修改几个字符,生成一些变体,然后分别尝试 match 查询。
    • 修改的字符数称为编辑距离。
    • 每个字符区分大小写。
    • 比如编辑距离为 1 时, "Leo" 可以生成以下变体:
      leo     # 替换一个字符
      Le      # 移除一个字符
      Lego    # 在任意位置插入一个字符
      Loe     # 交换两个相邻字符的位置
      
  • 例:
    GET /_search
    {
      "query": {
        "fuzzy": {
          "name": {
            "value": "Leo"          # 字段值需要与它模糊匹配
            # "fuzziness": "AUTO",  # 允许的最大编辑距离
            # "max_expansions": 50, # 允许的最大变体数
          }
        }
      }
    }
    

# sort

:用于控制文档的排序。

  • 例:
    GET /_search
    {
      "query": {
        "match_all": {}   # 匹配所有文档
      },
      "sort": [{          # 返回的文档按 age 的值升序排列
        "age": "asc"
      }],
      "from": 5,          # 返回从编号 5 开始的文档(从 0 开始编号)
      "size": 10          # 返回文档的最大数量(不能超过 max_result_window ),默认为 10 。比如查询命中 1000 个文档,只返回 10 个文档
    }
    

# filter

:用于添加过滤器,使 ES 只返回过滤后的文档。

  • 例:
    GET /_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "match": {
              "name": "Leo"
            }
          }
        }
      }
    }
    
    • query 参数中的查询子句称为“查询上下文”,filter 参数中的查询子句称为 “过滤器上下文” 。
    • ES 只会返回同时匹配 “查询上下文” 和 “过滤器上下文” 的文档,不过文档的 _score 的值只由 “查询上下文” 决定,不受 “过滤器上下文” 影响。

# bool

:用于进行布尔查询。

  • 例:
    GET /_search
    {
      "query": {
        "bool": {
          "must": [{
              "range": {
                "age": {
                  "gte": 10,
                  "lte": 20
                }
              }
            },
            {
              "bool": {
                "should": [{
                    "match": {
                      "name": "A"
                    }
                  },
                  {
                    "match": {
                      "name": "B"
                    }
                  }
                ],
                "minimum_should_match": 1
              }
            }
          ],
          "must_not": [{
            "match": {
              "age": "12"
            }
          }]
        }
      }
    }
    
    • match、exists、wildcard 等查询子句一次只能查询一个字段,查询多个字段时需要通过 bool 查询组合起来。
    • bool 查询中可以组合使用以下查询子句:
      • must 子句是一个列表,文档必须符合其中列出的所有条件。
      • must_not 子句是一个列表,文档必须不符合其中列出的所有条件。
      • should 子句是一个列表,文档应该符合其中列出的条件。不能写在与 must 同一层级的位置,否则会失效。
      • filter 子句
    • minimum_should_match 表示文档至少应该符合 should 列表中的几个条件。
      • 默认值为 1 。
      • 可以设置成任意正整数,比如 10 。不过实际生效的值不会超过 should 条件总数 n 。
      • 可以设置成任意负整数,比如 -2 ,这表示实际生效的值等于 n-2 。
      • 可以设置成百分比格式,比如 75% 、-25% ,这会向下取整。

# 聚合查询

:用于在查询时进行一些额外操作。

  • 聚合操作分为三类:
    • Metric
    • Bucket
    • Pipeline
  • 聚合查询中可以包含多个聚合操作。
    • Metric、Bucket 聚合中支持声明嵌套的子聚合查询。但 Pipeline 聚合不支持。

# Metric

:指标聚合。用于统计 sum、avg、max、min 等指标。

  • 例:统计 avg
    GET /_search
    {
      # "query": {},
      # "size": 0,              # 可以指定 size 为 0 ,使其不返回 query 的查询结果,只返回聚合结果
      "aggs": {                 # 启用聚合查询
        "my_agg_1": {           # 声明一个聚合操作,自定义名称
          "avg": {
            "field": "log.offset"
          }
        }
      }
    }
    
    响应示例:
    {
      "took" : 1,
      "hits" : { ... },
      "aggregations" : {
        "my_agg_1" : {
          "value" : 433553217
        }
      }
    }
    

# Bucket

:桶聚合。基于字段值等条件将文档分为多个组,称为桶。

  • 例:terms 聚合,是根据指定字段的非重复值来分组
    GET /_search
    {
      "aggs": {
        "my_agg_1": {
          "terms": {
            "field": "product",
            # "size": 10,                       # 限制最多返回的 bucket 数,默认为 10
            # "min_doc_count": 1,               # 每个 bucket 至少包含的文档数,不满足则不返回
            # "order": { "_count": "asc" },     # bucket 的排序方式
            # "show_term_doc_count_error": true # 是否在聚合结果中显示 sum_other_doc_count ,表示除了当前 bucket 以外的文档总数,包括分组失败的、bucket 排名超过 size 的
          }
        }
      }
    }
    

# Pipeline

:管道聚合。用于处理其它聚合操作的输出。

  • 例:avg_bucket 聚合,是统计一些 bucket 的平均值
    POST /_search
    {
      "aggs": {
        "my_agg_1": {
          "date_histogram": {
            "field": "date",
            "calendar_interval": "month"
          },
          "aggs": {
            "my_agg_2": {
              "sum": {
                "field": "price"
              }
            }
          }
        },
        "my_agg_3": {
          "avg_bucket": {
            "buckets_path": "my_agg_1>my_agg_2"    # 指定目标 bucket 的路径
          }
        }
      }
    }