Elasticsearch 核心查询解决 90% 查询场景

621 阅读8分钟

写在前面

官方文档地址

ES 的查询语言称为 DSL 即 Domain Specific Language 领域专用语言。

本文查询将以下列索引结构为例子展开。

PUT /dying_gq_bookstore
{
  "mappings": {
    "properties": {
      "book_id": {
        "type": "long"
      },
      "title": {
        "type": "text"
      },
      "tag": {
        "type": "keyword"
      },
      "book_type": {
        "type": "keyword"
      },
      "content": {
        "type": "text"
      },
      "status": {
        "type": "keyword"
      },
      "price": {
        "type": "long"
      },
      "stock":{
        "type": "long"
      }
      
    }
  }
}
POST /dying_gq_bookstore
{"index": {}}
{"book_id": 1,"title": "离开时请叫醒我","tag": "情感","book_type": "畅销文学","content": "不论如何,离开时请告诉我","status": 1,"price": "48","stock": 20000}
{"index": {}}
{"book_id": 1,"title": "我想要两个西柚","tag": "情感","book_type": "畅销文学","content": "i want to see you","status": 1,"price": "39","stock": 6000}
{"index": {}}
{"book_id": 1,"title": "清风徐来","tag": "情感","book_type": "畅销文学","content": "随波逐流","status": 1,"price": "36","stock": 18622}
{"index": {}}
{"book_id": 1,"title": "迷途","tag": "悬疑","book_type": "畅销文学","content": "生活如待宰羔羊","status": 2,"price": "54","stock": 543}
{"index": {}}
{"book_id": 1,"title": "八百万种死法","tag": "悬疑","book_type": "外国文学","content": "这个城市八百万种人,八百万种死法","status": 2,"price": "69","stock": 888}
{"index": {}}
{"book_id": 1,"title": "福尔摩斯与他的狗","tag": "悬疑","book_type": "外国文学","content": "这只狗已经老去好久了","status": 2,"price": "198","stock": 88932}
{"index": {}}
{"book_id": 1,"title": "只能陪你走一程","tag": "治愈","book_type": "治愈文学","content": "抱歉我只能陪你走到这了","status": 2,"price": "38","stock": 765}
{"index": {}}
{"book_id": 1,"title": "一点点","tag": "交通","book_type": "交通治安","content": "能拉但只能拉一点点","status": 1,"price": "99","stock": 18833}
{"index": {}}
{"book_id": 1,"title": "成华大道","tag": "交通","book_type": "交通治安","content": "我该不该走成华大道","status": 1,"price": "23","stock": 2334}
{"index": {}}
{"book_id": 1,"title": "华仙桥","tag": "交通","book_type": "交通治安","content": "华仙桥旅行指南","status": 1,"price": "19","stock": 210}

核心类型

  • 字符串:string,string类型包含 text 和 keyword。
    • text:该类型被用来索引长文本,在创建索引前会将这些文本进行分词,转化为词的组合,建立索引;允许es来检索这些词,text类型不能用来排序和聚合。
    • keyword:该类型不能分词,可以被用来检索过滤、排序和聚合,keyword类型不可用text进行分词模糊检索。
  • 数值型:long、integer、short、byte、double、float
  • 日期型:date

1.单条件查询

注以下部分查询会给出 类似 SQL 辅助理解,因倒排索引 term 匹配关系 SQL 是近似并非一定等效,一定注意

模糊匹配

以下模糊匹配都是基于 分词器 对 原本内容分词后 的 term 做匹配得出的效果。 (声明一下,一下所有查询都可以通过合适的语法组合起来进行更加复杂的查询)

match 分词模糊匹配

对搜索词分词后,查询匹配搜索列分词后的 term 如下例子 “你好呀世界” 可能会被分词为 “你好”、“世界”、“你”、“好”。(根据分词器的不同分词不同) 然后匹配倒排索引的 content 的 term。

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "match": {// 对搜索词分词后 匹配字段内容分词后的 term
      "content": "你好呀世界" // 字段 :匹配内容
    }
  }
}

类似 SQL:SELECT * FROM dying_gq_bookstore WHERE content LIKE '%你好呀世界%' LIMIT 0,20

prefix 前缀模糊匹配

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "prefix": {// 匹配字段内容分词后 term 的前缀
      "content": "你好呀世界" // 字段 :匹配内容
    }
  }
}

类似 SQL:SELECT * FROM dying_gq_bookstore WHERE content LIKE '你好呀世界%' LIMIT 0,20

regexp 正则匹配

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "regexp": {// 分词 正则匹配字段内容分词 term 
      "content": "[1-9]" // 字段 :匹配内容
    }
  }
}

精确匹配

term 单个字段单条件精确匹配

精确匹配分词后的 term,和 match 的区别在于不会对你要匹配的词进行分词。

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "match": {// 匹配字段内容分词后的 term
      "content": "你好呀世界" // 字段 :匹配内容
    }
  }
}

类似 SQL:SELECT * FROM dying_gq_bookstore WHERE content = '你好呀世界' LIMIT 0,20

terms 单个字段多条件精确匹配

数组内的值相当于 or 的关系

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "terms": {
      "content": [
        "你好",
        "时间",
        "不错"
      ]
    }
  }
}

类似 SQL:SELECT * FROM dying_gq_bookstore WHERE content = '你好' OR content = '时间' OR content = '不错' LIMIT 0,20

range 范围内精确匹配

POST /dying_gq_bookstore/_search
{
  "from": 0, //分页 从 0 个开始
  "size": 20, // 后 20 条数据 意义同 mysql limit 0,20
  "query": {
    "range": {
      "book_id": {
        "gte": 10, //大于
        "lte": 20 //小于
      }
    }
  }
}

类似 SQL:SELECT * FROM dying_gq_bookstore WHERE between 10 and 20 LIMIT 0,20

2.组合条件查询

bool 查询

可组合条件:

must : 各个条件都必须满足,即各条件是and的关系 should : 各个条件有一个满足即可,即各条件是or的关系 must_not : 不满足所有条件,即各条件是not的关系 filter : 不计算相关度评分,它不计算_score即相关度评分,效率更高

POST /dying_gq_bookstore/_search
{
  "query": { 
    "bool": { 
      "must": [
        { "match": { "title":   "Search"        }},
        { "match": { "content": "Elasticsearch" }}
      ],
      "filter": [ 
        { "term":  { "status": "published" }},
        { "range": { "book_id": { "gte": "200" }}}
      ]
    }
  }
}

3.聚合搜索

bucket: bucket 为分组后个每个桶 metric: metric 为每个桶中的统计分析(如 数量、最大值、最小值等)

注意 text 类型无法聚合

样例 1 :普通聚合

样例 1 查询语义为:

对 dying_gq_bookstore 索引按字段 tag 分组聚合 ,统计不同 tag 的数量并 按数量 升序排序。

POST /dying_gq_bookstore/_search
{
  "size": 0, // 此处设置 size = 0 表示不返回 ES 数据文档,仅返回聚合后数据
  "aggs": { // 聚合
    "group_by_tag": { // 聚合名称,可以自定义名称
      "terms": {//聚合匹配方式
        "field": "tag", // 列名
        "order": { // 排序
          "_count": "asc" // 根据个数 升序 _count 为固定写法
        }
      }
    }
  }
}

样例 2 : 嵌套聚合 下钻分析

样例 2 查询语义为:

对 dying_gq_bookstore 索引按字段 tag 分组聚合 。并在此基础上对每个桶做统计分析即 metric ,求每个 bucket tag 分组下的书籍平均价格 并 按平均价格 升序排序。这种嵌套聚合的方式又称为 下钻分析

POST /dying_gq_bookstore/_search
{
  "size": 0, 
  "aggs": { // 聚合
    "group_by_tag": { // 聚合名称,可以自定义名称
      "terms": {//聚合匹配方式
        "field": "tag", // 列名
        "order": { // 排序
          "avg_by_price": "asc" // 求取的平均价格 升序 这里命名的作用就显示出来了
        }
      },
      "aggs": { // 再次聚合 嵌套聚合 下钻分析
        "avg_by_price": { // 聚合名称
          "avg": { // 求平均值
            "field": "price" // 平均值列名称
          }
        }
      }
    }
  }
}

样例 3 : 多重嵌套聚合

样例 3 查询语义为:

以 tag 标签分组后 分析该标签下书籍平均价格,并以 tag 分组的 bucket 为基础 以 book_type 再进行子分组,子分组以 book_type 平均价格升序排序,外部 tag 分组以 tag 平均价格升序排序。

POST /dying_gq_bookstore/_search
{
  "size": 0, 
  "aggs": { 
    "group_by_tag": { 
      "terms": {
        "field": "tag", 
        "order": { 
          "avg_by_price_tag": "asc"  
        }
      },
      "aggs": { 
        "avg_by_price_tag": { 
          "avg": { 
            "field": "price" 
          }
        },
        "group_by_book_type":{
          "terms": {
            "field": "book_type",
            "order": {
              "avg_by_price_book_type": "asc"
            }
          },
          "aggs": {
            "avg_by_price_book_type": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

为帮助理解,这里贴出查询结果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_tag" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "治愈",
          "doc_count" : 1,
          "avg_by_price_tag" : {
            "value" : 38.0
          },
          "group_by_book_type" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "治愈文学",
                "doc_count" : 1,
                "avg_by_price_book_type" : {
                  "value" : 38.0
                }
              }
            ]
          }
        },
        {
          "key" : "情感",
          "doc_count" : 3,
          "avg_by_price_tag" : {
            "value" : 41.0
          },
          "group_by_book_type" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "畅销文学",
                "doc_count" : 3,
                "avg_by_price_book_type" : {
                  "value" : 41.0
                }
              }
            ]
          }
        },
        {
          "key" : "交通",
          "doc_count" : 3,
          "avg_by_price_tag" : {
            "value" : 47.0
          },
          "group_by_book_type" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "交通治安",
                "doc_count" : 3,
                "avg_by_price_book_type" : {
                  "value" : 47.0
                }
              }
            ]
          }
        },
        {
          "key" : "悬疑",
          "doc_count" : 3,
          "avg_by_price_tag" : {
            "value" : 107.0
          },
          "group_by_book_type" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "畅销文学",
                "doc_count" : 1,
                "avg_by_price_book_type" : {
                  "value" : 54.0
                }
              },
              {
                "key" : "外国文学",
                "doc_count" : 2,
                "avg_by_price_book_type" : {
                  "value" : 133.5
                }
              }
            ]
          }
        }
      ]
    }
  }
}

样例 4 : 聚合多分析

样例 4 查询语义为:

以 book_type 分组,统计分析组内 价格最大值、最小值、和总值

POST /dying_gq_bookstore/_search
{
  "size": 0,
  "aggs": {
    "group_by_book_type": {
      "terms": {
        "field": "book_type",
        "order": {
          "price_min": "asc"
        }
      },
      "aggs": {
        "price_max": {
          "max": {
            "field": "price"
          }
        },
        "price_min":{
          "min": {
            "field": "price"
          }
        },
        "price_sum":{
          "sum": {
            "field": "price"
          }
        }
      }
      
    }
  }
}

样例 5 : 聚合分组最大值

样例 5 查询语义为:

以 book_type 分组,统计分析组内价格最大的书籍并展示

POST /dying_gq_bookstore/_search
{
  "size": 0,
  "aggs": {
    "group_by_book_type": {
      "terms": {
        "field": "book_type"
      },
      "aggs": {
        "top_price": {
          "top_hits": {
            "size": 1,
            "sort": [{
              "price": {
                "order": "desc"
              }
            }]
          }
        }
      }
      
    }
  }
}

样例 6 : 区间分组统计

样例 6 查询语义为:

以 price 书籍价格分组 分组间隔为 20 :[0,20) [20,40) [40,60) ……。并统计分组内最大书籍价格

POST /dying_gq_bookstore/_search
{
  "size": 0,
  "aggs": {
    "histogram_by_price": {
      "histogram": {
        "field": "price",
        "interval": 20 // 分组间隔 
      },
      "aggs": {
        "max_by_price": {
          "max": {
            "field": "price"
          }
        }
      }
      
    }
  }
}

为帮助理解,这里贴出查询结果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "histogram_by_price" : {
      "buckets" : [
        {
          "key" : 0.0,
          "doc_count" : 1,
          "max_by_price" : {
            "value" : 19.0
          }
        },
        {
          "key" : 20.0,
          "doc_count" : 4,
          "max_by_price" : {
            "value" : 39.0
          }
        },
        {
          "key" : 40.0,
          "doc_count" : 2,
          "max_by_price" : {
            "value" : 54.0
          }
        },
        {
          "key" : 60.0,
          "doc_count" : 1,
          "max_by_price" : {
            "value" : 69.0
          }
        },
        {
          "key" : 80.0,
          "doc_count" : 1,
          "max_by_price" : {
            "value" : 99.0
          }
        },
        {
          "key" : 100.0,
          "doc_count" : 0,
          "max_by_price" : {
            "value" : null
          }
        },
        {
          "key" : 120.0,
          "doc_count" : 0,
          "max_by_price" : {
            "value" : null
          }
        },
        {
          "key" : 140.0,
          "doc_count" : 0,
          "max_by_price" : {
            "value" : null
          }
        },
        {
          "key" : 160.0,
          "doc_count" : 0,
          "max_by_price" : {
            "value" : null
          }
        },
        {
          "key" : 180.0,
          "doc_count" : 1,
          "max_by_price" : {
            "value" : 198.0
          }
        }
      ]
    }
  }
}

补充:查询去重

此处语义为:查询前 5 条书籍价格最高的,并按 tag 去重

POST /dying_gq_bookstore/_search
{
  "size": 5,
  "collapse": { // 指定去重字段
    "field": "tag"
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
  
}

总结

总体来说 ES 的查询 DSL 为我们提供了丰富的语法和聚合语义。

基础查询 使用 query 对应常用查询条件 matchprefixregexptremtremsrange

组合条件 bool 条件连接 mustshouldmust_notfilter 同时配合上述 query 条件进行查询。

聚合分析 aggs 聚合条件 tremsavgsummaxmintop_hitshistogramdate_histogram

这三大类相互 嵌套组合 基本可以覆盖我们绝大多数场景了。更多进阶用法可以到官网进行学习查询。官方文档地址

我是 dying 搁浅 我看了一场 97 小时的电影也没能等来你的点赞关注收藏,我想这不是你不够喜欢我,而是我看的电影不够长……

在这里插入图片描述