Apache Druid groupBy 查询结果过滤:Having 子句(HavingSpec)完整实战指南
2026/9/23 23:14:36 网站建设 项目流程
  • 数据库
  • 数据分析
  • OLAP
  • 大数据
  • 实时分析
  • 数据仓库
  • 后端

【免费下载链接】druid

Apache Druid: a high performance real-time analytics database.

项目地址:https://gitcode.com/gh_mirrors/druid7/druid
点击查看免费下载

Having 子句是 Apache Druid 中用于对 groupBy 查询聚合结果做行级过滤的 JSON 对象,其语义与 SQL 的HAVING子句等价,常用来实现"只返回满足聚合值条件的分组"这类查询需求。本文以 docs/content/querying/having.md 为主体骨架,完整覆盖其全部过滤类型与 JSON 语法,并结合 processing 模块下的HavingSpec源码实现、GroupByQuery 查询解析逻辑及对应测试,讲透每种过滤器的配置方式、底层比较规则与使用注意事项,帮助你在实际查询中正确编写和调试 having 子句。

一、Having 子句是什么

在 SQL 中,WHERE作用于原始行,而HAVING作用于聚合后的分组结果。Druid 的 having 子句与之对应:它也是一个 JSON 对象,通过指定聚合值上的条件,决定 groupBy 查询返回哪些行(即哪些分组)。以 GroupByQuery.java 中的定义为依据,having 子句作为 groupBy 查询 JSON 的一个可选顶层属性存在:

@JsonProperty("having") HavingSpec havingSpec,

在查询执行阶段,Druid 会先完成分组与聚合,再对每个结果行调用HavingSpec.eval(Row)判断该行是否满足条件(见 GroupByQuery.java),只有eval返回true的行才会进入最终结果。也就是说,having 过滤发生在聚合之后、limit 截断之前。

Druid 通过@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")type字段反序列化不同的 having 子句,HavingSpec接口(HavingSpec.java)中注册的全部类型如下:

type取值对应实现类语义
andAndHavingSpec逻辑与
orOrHavingSpec逻辑或
notNotHavingSpec逻辑非
greaterThanGreaterThanHavingSpec聚合值大于给定值
lessThanLessThanHavingSpec聚合值小于给定值
equalToEqualToHavingSpec聚合值等于给定值
dimSelectorDimensionSelectorHavingSpec维度值等于给定值
alwaysAlwaysHavingSpec恒真(主要用于测试组合)
filterDimFilterHavingSpec复用任意 Druid 查询过滤器

其中alwaysnot对应的NeverHavingSpecAlwaysHavingSpec被作为组合单元暴露在HavingSpec接口中(HavingSpec.NEVER/HavingSpec.ALWAYS,见 HavingSpec.java),主要用于逻辑组合与测试。下面按类别逐一介绍每种语法的 JSON 写法。

二、查询过滤器(Query Filter HavingSpec,type 为 filter)

查询过滤器类型的 HavingSpec 允许把任意 Druid 查询过滤器直接用在 having 部分。其语法为:

{ "type" : "filter", "filter" : <any Druid query filter> }

例如使用一个 selector 过滤器:

{ "type" : "filter", "filter" : { "type": "selector", "dimension" : "<dimension>", "value" : "<dimension_value>" } }

从源码实现看(DimFilterHavingSpec.java),filter属性是必填的(构造器通过Preconditions.checkNotNull(dimFilter, "filter")校验),它会把传入的DimFilter转成ValueMatcher后再对每一行做匹配,因此凡是 filters.md 中列出的过滤器类型(selector、regex、bound、javascript 等)都可在此复用。

特别注意__time字段的差异filter类型的 HavingSpec 作用于输出字段名为__time时,行为与其他 HavingSpec 不同——它作用于每一行的时间戳,而不是名为__time的输出字段。官方文档明确建议:避免将输出字段命名为__time,未来版本的 Druid 可能会强制执行这一约束。

三、数值过滤器(Numeric Filters)

数值过滤器是最简单的 having 子句,直接对聚合指标与给定数值做比较,同时也可作为更复杂的布尔表达式过滤器的底层基础。示例:

{ "type": "greaterThan", "aggregation": "myAggMetric", "value": 100 }

其中aggregation指定要比较的聚合指标名(即 groupBy 查询中aggregations数组里定义的输出名),value为数值。下面介绍三种数值过滤器。

3.1 equalTo:等于

匹配聚合值等于给定值的行:

{ "type": "equalTo", "aggregation": "<aggregate_metric>", "value": <numeric_value> }

等价于 SQL 的HAVING <aggregate> = <value>

3.2 greaterThan:大于

匹配聚合值大于给定值的行:

{ "type": "greaterThan", "aggregation": "<aggregate_metric>", "value": <numeric_value> }

等价于 SQL 的HAVING <aggregate> > <value>

3.3 lessThan:小于

匹配聚合值小于给定值的行:

{ "type": "lessThan", "aggregation": "<aggregate_metric>", "value": <numeric_value> }

等价于 SQL 的HAVING <aggregate> < <value>

3.4 数值比较的底层实现

三种数值过滤器最终都通过同一个工具类HavingSpecMetricComparator(HavingSpecMetricComparator.java)完成比较,其规则如下:

  • 若聚合结果以Long存储,则用Longs.compare(long, value.longValue())做整型比较;
  • 若聚合结果是字符串且完全匹配整型正则[-|+]?\d+LONG_PAT),则按getLongMetric取出的长整型值比较;
  • 其他情况(浮点等)统一回退为Floats.compare(float, value.floatValue())的浮点比较。

因此当聚合指标值很大(超出浮点精度)或希望做精确整型比较时,Druid 会优先走整型路径,避免精度损失。对应的序列化/反序列化与组合行为在 HavingSpecTest.java 中有完整测试覆盖(例如GreaterThanHavingSpec("agg", 1.3)LessThanHavingSpecNotHavingSpec组合的序列化验证)。

四、维度选择过滤器(Dimension Selector Filter)

dimSelector类型的 having 子句匹配维度值等于给定值的行:

{ "type": "dimSelector", "dimension": "<dimension>", "value": <dimension_value> }

注意它与数值过滤器不同:数值过滤器比较的是聚合指标,而dimSelector比较的是分组维度本身。从实现看(DimensionSelectorHavingSpec.java):

  • dimension为必填属性(构造器通过checkNotNull校验);
  • 可选属性extractionFn用于在比较前对维度值做提取转换,未指定时默认使用IdentityExtractionFn(恒等变换);
  • 由于 Druid 支持多值维度,eval会遍历该维度行的所有取值,任一值经extractionFn提取后与value相等即命中;若维度行值为空且value也为 null/空串,同样视为命中。

因此可以在 having 阶段结合 extraction functions 做大小写归一、正则提取等复杂匹配。

五、逻辑表达式过滤器(Logical Expression Filters)

与 SQL 的AND/OR/NOT对应,Druid 提供三种逻辑组合器,其内部havingSpecs/havingSpec可以嵌套任意本页介绍的其他 having 子句(包括再嵌套逻辑组合)。

5.1 AND

{ "type": "and", "havingSpecs": [<having clause>, <having clause>, ...] }

havingSpecs中的每个子句可以是本页定义的任意 having 子句。从 AndHavingSpec.java 的实现看,它是短路求值的:遍历子句,一旦某个子句eval返回false立即返回false

5.2 OR

{ "type": "or", "havingSpecs": [<having clause>, <having clause>, ...] }

同样支持任意嵌套。对应 OrHavingSpec.java 的实现也是短路求值:任一子句eval返回true即整体为true

5.3 NOT

{ "type": "not", "havingSpec": <having clause> }

对单个子句的结果取反(见 NotHavingSpec.java)。

5.4 组合示例

利用逻辑组合可以写出等价于复杂 SQL 的过滤条件。例如"返回count大于 100 且(avg_price小于 10 或category等于 'book')":

{ "type": "and", "havingSpecs": [ { "type": "greaterThan", "aggregation": "count", "value": 100 }, { "type": "or", "havingSpecs": [ { "type": "lessThan", "aggregation": "avg_price", "value": 10 }, { "type": "dimSelector", "dimension": "category", "value": "book" } ] } ] }

六、完整 groupBy 查询示例

把 having 子句放入 groupBy 查询 JSON 的having顶层属性,即可对聚合结果过滤。下面是一个完整的查询(聚合定义参考 aggregations.md,groupBy 查询整体结构参考 groupbyquery.md):

{ "queryType": "groupBy", "dataSource": "wikiticker", "granularity": "day", "dimensions": ["channel"], "aggregations": [ {"type": "count", "name": "edits"}, {"type": "longSum", "name": "added", "fieldName": "added"} ], "having": { "type": "and", "havingSpecs": [ {"type": "greaterThan", "aggregation": "edits", "value": 50}, {"type": "lessThan", "aggregation": "added", "value": 100000} ] }, "intervals": ["2015-09-12T00:00:00.000Z/2015-09-13T00:00:00.000Z"] }

该查询按channel分组统计每日编辑次数与新增字符数,最终只返回"编辑次数大于 50 且新增字符数小于 100000"的频道分组,效果等价于 SQL 的GROUP BY channel HAVING edits > 50 AND added < 100000

七、使用注意事项与性能提示

  • 线程安全HavingSpec对象本身不是线程安全的,javadoc 与 GroupByQuery.java 的applyLimit注释均明确提示:不应被多个线程同时使用,也不要在两个不同线程中同时累积本方法返回的两个SequenceDimFilterHavingSpec更是在eval内部通过evalCount自增检查检测并发调用,一旦发现并发会抛出IllegalStateException("concurrent 'eval' calls not permitted!")(见 DimFilterHavingSpec.java)。
  • 输出字段命名:避免把输出字段命名为__time,因为filter类型 HavingSpec 对它存在特殊语义(作用于行时间戳而非字段值)。
  • 比较精度:数值比较优先走整型路径(见第三节),对长整型聚合结果更精确;浮点场景则按 float 比较,若对精度敏感需在设计聚合时留意。
  • 缓存:每种 HavingSpec 都实现了getCacheKey()(如equalTo0x3greaterThan0x4lessThan0x5dimSelector0x8filter0x9),参与查询缓存键的生成,因此不同 having 条件会生成不同的缓存条目,可放心使用。

八、进一步阅读

  • 本文主体:docs/content/querying/having.md
  • 可复用的查询过滤器清单:docs/content/querying/filters.md
  • groupBy 查询完整结构与其余属性:docs/content/querying/groupbyquery.md
  • 聚合定义与输出名:docs/content/querying/aggregations.md
  • HavingSpec 接口与类型注册:processing/src/main/java/io/druid/query/groupby/having/HavingSpec.java
  • 查询解析与执行入口:processing/src/main/java/io/druid/query/groupby/GroupByQuery.java
  • 序列化与组合行为测试:processing/src/test/java/io/druid/query/groupby/having/HavingSpecTest.java
  • 数据库
  • 数据分析
  • OLAP
  • 大数据
  • 实时分析
  • 数据仓库
  • 后端

【免费下载链接】druid

Apache Druid: a high performance real-time analytics database.

项目地址:https://gitcode.com/gh_mirrors/druid7/druid
点击查看免费下载
上一篇:react-jsonschema-form中的表单数据处理完成回调
下一篇:Odyssey Theme:如何用 Astro 打造完美 Lighthouse 评分的现代商业网站

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询