ES中如何实现类似having的先聚合再过滤查询

简介: ES中如何实现类似having的先聚合再过滤查询

一、问题描述:


在mysql数据库中,我们可以很方面的通过having关键字实现对聚合结果的过滤查询。那么,在ES中该如何实现类似having的先聚合再过滤查询呢?


二、业务场景:


需要找出下单次数大于等于2单,并且平均下单金额大于等于100的客户


在关系型数据库中对应的SQL语句:

SELECT 
    userId,
    AVG(amount) avgAmount,
    count(*) orderCount
FROM  order
GROUP by userId
HAVING avgAmount >= 100 and orderCount >=2


三、数据准备


创建订单索引order_index,并添加测试数据。

## 删除索引
## DELETE order_index
## 新建索引
PUT order_index
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "amount": {
        "type": "integer"
      }
    }
  }
}
## 添加数据
POST order_index/_bulk?refresh
{ "create": { } }
{ "name": "老万", "amount": 100}
{ "create": { } }
{ "name": "老万", "amount": 80}
{ "create": { } }
{ "name": "老万", "amount": 300}
{ "create": { } }
{ "name": "老王", "amount": 45}
{ "create": { } }
{ "name": "小明", "amount": 15}
{ "create": { } }
{ "name": "小明", "amount": 50}
{ "create": { } }
{ "name": "小红", "amount": 300}


四、具体实现


1、SQL实现方式

说明:由于ES6.3以后已经支持sql查询,所有首先尝试大家最熟悉的sql查询方案能否实现。

POST /_sql?format=txt
{
  "query": "SELECT name,AVG(amount) avgAmount,count(*) orderCount FROM order_index group by name having avgAmount >= 100 and orderCount >=2 "
}


查询结果:

用户名为老万,满足平均订单金额大于100,且下单数大于2。

查询结果正确。

71.png

2、DSL实现方式

GET order_index/_search
{
  "size": 0,
  "aggs": {
    "groupName": {
      "terms": {
        "field": "name"
      },
      "aggs": {
        "avgAmount": {
          "avg": {
            "field": "amount"
          }
        },
        "having": {
          "bucket_selector": {
            "buckets_path": {
              "orderCount": "_count",
              "avgAmount": "avgAmount"
            },
            "script": {
              "source": "params.avgAmount >= 100 && params.orderCount >=2 "
            }
          }
        }
      }
    }
  }
}

查询结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 7,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "groupUserId" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "老万",
          "doc_count" : 3,
          "avgAmount" : {
            "value" : 160.0
          }
        }
      ]
    }
  }
}


sql语句底层实现分析:


POST /_sql/translate
{
  "query": "SELECT name,AVG(amount) avgAmount,count(*) orderCount FROM order_index group by name having avgAmount >= 100 and orderCount >=2 "
}


执行结果:

分析sql转化的DSL语句,和上面DSL语句的实现,说明两者底层实现原理一致。

mysql中通过having实现根据聚合结果进行过滤,ES中使用 bucket_selector 来实现此功能。

{
  "size" : 0,
  "_source" : false,
  "stored_fields" : "_none_",
  "aggregations" : {
    "groupby" : {
      "composite" : {
        "size" : 1000,
        "sources" : [
          {
            "7e80e5b2" : {
              "terms" : {
                "field" : "name",
                "missing_bucket" : true,
                "order" : "asc"
              }
            }
          }
        ]
      },
      "aggregations" : {
        "d8415567" : {
          "avg" : {
            "field" : "amount"
          }
        },
        "having.having.d8415567_&_having.b26c7698" : {
          "bucket_selector" : {
            "buckets_path" : {
              "a0" : "d8415567",
              "a1" : "_count"
            },
            "script" : {
              "source" : "InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.and(InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.gte(params.a0,params.v0)),InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.gte(params.a1,params.v1))))",
              "lang" : "painless",
              "params" : {
                "v0" : 100,
                "v1" : 2
              }
            },
            "gap_policy" : "skip"
          }
        }
      }
    }
  }
}

具体实现


本文主要介绍了ES中如何实现类似having的先聚合再过滤查询。

1、介绍了基于sql和dsl的两种实现方式,但是二者的底层原理其实都是一样的。

2、实际项目中,更推荐直接采用sql来实现,代码简单,sql语句相比dsl上手更容易,也更容易理解。

3、mysql中通过having实现根据聚合结果进行过滤,ES中使用 bucket_selector 来实现此功能。

目录
相关文章
|
存储 消息中间件 SQL
Flink 必知必会经典课程8:Flink Connector 详解
关于Flink Connector的详解,本文将通过四部分展开介绍:1. 连接器;2. Source API;3. Sink API;4. Collector的未来发展。
Flink 必知必会经典课程8:Flink Connector 详解
|
设计模式 供应链
一文教会你如何写复杂业务代码
了解我的人都知道,我一直在致力于应用架构和代码复杂度的治理。 这两天在看零售通商品域的代码。面对零售通如此复杂的业务场景,如何在架构和代码层面进行应对,是一个新课题。针对该命题,我进行了比较细致的思考和研究。
39079 3
|
机器学习/深度学习 数据采集 人工智能
深度学习实战指南:从神经网络基础到模型优化的完整攻略
🌟 蒋星熠Jaxonic,AI探索者。深耕深度学习,从神经网络到Transformer,用代码践行智能革命。分享实战经验,助你构建CV、NLP模型,共赴二进制星辰大海。
|
机器学习/深度学习 存储 算法
Trinity-RFT:构建智能体持续学习的自动化强化微调工厂
大型语言模型作为智能体在真实环境中持续交互学习面临诸多挑战。 Trinity-RFT 是通义实验室推出的强化微调框架,旨在实现智能体的持续进化。它通过探索、训练与经验池的解耦设计,支持多样化训练模式,提升资源利用率和学习稳定性。同时,Trinity-RFT 提供灵活的数据处理与算法模块化功能,降低应用与研究门槛,助力迈向终身学习与自主进化的智能体时代。
1218 2
|
存储 消息中间件 安全
企业级实时消息推送系统的架构设计,一文即懂!
如果你是技术负责人,该如何搭建一套能解决这些问题的企业级统一消息推送平台?今天我们就从核心挑战出发,拆解一套可落地的统一推送服务架构方案。
1775 0
|
缓存 NoSQL 安全
|
关系型数据库 MySQL
MySql 计算两个日期的时间差函数
MySql计算两个日期的时间差函数 MySql计算两个日期的时间差函数TIMESTAMPDIFF用法: 语法: TIMESTAMPDIFF(interval,datetime_expr1,datetime_expr2) 说明: 返回日期或日期时间表达式datetime_expr1 和datetime_expr2the 之间的整数差。
14141 1
|
Java BI 程序员
「软件项目管理」成本估算模型——Walston-Felix模型和COCOMO Ⅱ模型
该文章深入探讨了两种软件项目成本估算模型——Walston-Felix模型和COCOMO II模型,详细解释了各自的计算公式、应用背景及步骤,并通过具体示例展示了如何使用这两种模型来进行准确的成本预测。
「软件项目管理」成本估算模型——Walston-Felix模型和COCOMO Ⅱ模型
|
机器学习/深度学习 人工智能 监控
如何评估AI教育效果?
【6月更文挑战第1天】如何评估AI教育效果?
1799 3
|
存储 自然语言处理 关系型数据库
Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别
【7月更文挑战第3天】Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别