- 数据库
- 数据分析
- OLAP
- 大数据
- 实时分析
- 数据仓库
- 后端
【免费下载链接】druid
Apache Druid: a high performance real-time analytics database.
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取值 | 对应实现类 | 语义 |
|---|---|---|
and | AndHavingSpec | 逻辑与 |
or | OrHavingSpec | 逻辑或 |
not | NotHavingSpec | 逻辑非 |
greaterThan | GreaterThanHavingSpec | 聚合值大于给定值 |
lessThan | LessThanHavingSpec | 聚合值小于给定值 |
equalTo | EqualToHavingSpec | 聚合值等于给定值 |
dimSelector | DimensionSelectorHavingSpec | 维度值等于给定值 |
always | AlwaysHavingSpec | 恒真(主要用于测试组合) |
filter | DimFilterHavingSpec | 复用任意 Druid 查询过滤器 |
其中always和not对应的NeverHavingSpec、AlwaysHavingSpec被作为组合单元暴露在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)、LessThanHavingSpec与NotHavingSpec组合的序列化验证)。
四、维度选择过滤器(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注释均明确提示:不应被多个线程同时使用,也不要在两个不同线程中同时累积本方法返回的两个Sequence。DimFilterHavingSpec更是在eval内部通过evalCount自增检查检测并发调用,一旦发现并发会抛出IllegalStateException("concurrent 'eval' calls not permitted!")(见 DimFilterHavingSpec.java)。 - 输出字段命名:避免把输出字段命名为
__time,因为filter类型 HavingSpec 对它存在特殊语义(作用于行时间戳而非字段值)。 - 比较精度:数值比较优先走整型路径(见第三节),对长整型聚合结果更精确;浮点场景则按 float 比较,若对精度敏感需在设计聚合时留意。
- 缓存:每种 HavingSpec 都实现了
getCacheKey()(如equalTo为0x3、greaterThan为0x4、lessThan为0x5、dimSelector为0x8、filter为0x9),参与查询缓存键的生成,因此不同 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.
相关推荐
Apache Druid groupBy 查询 Having 子句完全指南:聚合后过滤的 JSON 语法与源码实现
Apache Druid groupBy 查询 Having 子句完全指南:聚合后过滤的 JSON 语法与源码实现 本篇技术指南聚焦 Apache Druid
数据库OLAP大数据后端Apache Druid groupBy 查询的 LimitSpec 完全指南:排序与结果限制
Apache Druid groupBy 查询的 LimitSpec 完全指南:排序与结果限制 limitSpec 是 Apache Druid 原生(nati
数据库OLAP大数据后端Apache Druid 查询指南:使用 LimitSpec 对 groupBy 查询结果排序与限量
Apache Druid 查询指南:使用 LimitSpec 对 groupBy 查询结果排序与限量 limitSpec 是 Apache Druid grou
数据库数据分析OLAP大数据实时分析数据仓库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考