Apache Druid ANSI SQL 兼容模式迁移指南:从遗留 null 处理到 SQL 三值逻辑
Apache Druid ANSI SQL 兼容模式迁移指南从遗留 null 处理到 SQL 三值逻辑【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid6/druidApache Druid 自 28.0.0 起默认的 null 处理模式已切换为与 ANSI SQL 标准对齐SQL compliant mode。本指南面向依赖旧版legacynull 处理行为的 Druid 运维与开发人员系统说明新旧两种模式的区别、三组核心运行时配置的作用、以及三条可落地的迁移路径在摄入阶段替换 null、在摄入阶段将空字符串强转为 null、以及重写查询使其符合 ANSI SQL 语义。读完本文你将能依据自身业务对 null 的保留需求选择并实施从遗留模式平滑过渡到 SQL 兼容模式的完整方案。背景为什么默认 null 处理会改变在 Apache Druid 28.0.0 之前Druid 默认使用一种遗留模式legacy mode摄入数据时把 null 替换为默认值。这种模式带来的后果是字符串列无法区分空字符串与 null两者被视为同一值数值列无法表示 null 行null在存储时被写为0。自 28.0.0 起Druid 默认以 ANSI SQL 兼容模式写入段segment字符串维度将 null 与空字符串区分存储数值维度将 null 与 0 区分存储。由于 ANSI SQL 标准规定任何与 null 的比较结果都是 unknown三值逻辑x some value只会返回非 null 的行——这会直接影响依赖旧行为的应用查询结果。遗留模式已被标记为废弃并计划移除因此掌握迁移策略对使用 Druid 的团队来说是必经之路。三组核心配置SQL 兼容模式的开关Druid 28.0.0 及之后版本中开启 ANSI SQL 兼容 null 处理模式的默认配置如下| 配置项 | 默认值 | 作用 | | -- | -- | -- | |druid.generic.useDefaultValueForNull|false| 是否将 null 替换为默认值字符串为空串、数值为 0。false表示保留 null | |druid.expressions.useStrictBooleans|true| 表达式求值是否使用严格布尔逻辑并统一用 1 表示 true、0 表示 false | |druid.generic.useThreeValueLogicForNativeFilters|true| 原生过滤器是否使用三值逻辑TRUE / FALSE / UNKNOWN |遗留已废弃模式对应的配置是| 配置项 | 遗留值 | | -- | -- | |druid.generic.useDefaultValueForNull|true| |druid.expressions.useStrictBooleans|false| |druid.generic.useThreeValueLogicForNativeFilters|true|这些配置已被标记为废弃并排入移除计划。在配置被移除后Druid 会忽略配置文件中残留的这些项直接使用默认的 SQL 兼容模式。从源码看这三项配置的解析与默认值分别位于NullValueHandlingConfig.java定义了druid.generic.useDefaultValueForNull与druid.generic.useThreeValueLogicForNativeFilters两个系统属性未显式设置时分别回退到false与true当useDefaultValueForNulltrue或useThreeValueLogicForNativeFiltersfalse时还会在启动日志中输出建议使用 SQL 兼容行为的警告。ExpressionProcessingConfig.java定义了druid.expressions.useStrictBooleans未显式设置时回退到true设为false时同样输出废弃警告。值得说明的是原生过滤器是否真正启用三值逻辑并不是只看useThreeValueLogicForNativeFilters一项。在 NullHandling.java 中useThreeValueLogic()返回sqlCompatible() useThreeValueLogicForNativeFilters useStrictBooleans()——即三个条件同时满足才启用三值逻辑。NotFilter在反转匹配结果时依赖该开关的includeUnknown参数以保证NOT语义符合 SQL 标准。因此可以推断只要这三项中任何一项被设置为非默认值基于非表达式的过滤器就会退化为两值逻辑表达式类过滤器则单独由druid.expressions.useStrictBooleans控制。关于 null 处理在查询层面的语义细节如COUNT(*)与COUNT(expr)的差异、GROUP BY对 null 与空串的区分可以参考 sql-data-types.md存储层面的实现细节参见 segments.md。两种迁移路线按是否保留 null 来选择如果业务逻辑依赖遗留模式的 null 行为迁移到 SQL 兼容模式有两条路线改造摄入数据避免 null在摄入阶段把字符串列的 null 替换为空字符串、把数值列的 null 替换为 0从而让现有查询在新模式下得到与遗留模式一致的结果。适合不关心是否保留 null 的场景。操作手段是修改摄入 SQL 查询与摄入 spec。保留 null重写查询使其符合 ANSI SQL原样保留含 null 的数据但把所有受影响的客户端查询改写为 ANSI SQL 兼容写法。适合有硬性需求必须保留 null 的场景。下文分别给出这两条路线的完整实操示例。路线一A摄入时用 COALESCE / NVL 替换 null如果不需在 Druid 内保留 null可以在摄入时用 transform 将 null 替换为其他值。考虑如下输入数据{time:2024-01-01T00:00:00.000Z,string_example:my_string,number_example:99} {time:2024-01-02T00:00:00.000Z,string_example:,number_example:0} {time:2024-01-03T00:00:00.000Z,string_example:null,number_example:null}下面分别给出 SQL 批量摄入与 JSON 批量摄入两种写法均使用COALESCE把字符串 null 换成空串、用NVL把数值 null 换成 0。SQL-based batchREPLACE INTO no_nulls_example OVERWRITE ALL WITH ext AS ( SELECT * FROM TABLE( EXTERN( {type:inline,data:{\time\:\2024-01-01T00:00:00.000Z\,\string_example\:\my_string\,\number_example\:99}\n{\time\:\2024-01-02T00:00:00.000Z\,\string_example\:\\,\number_example\:0}\n{\time\:\2024-01-03T00:00:00.000Z\,\string_example\:null,\number_example\:null}}, {type:json} ) ) EXTEND (time VARCHAR, string_example VARCHAR, number_example BIGINT) ) SELECT TIME_PARSE(time) AS __time, -- Replace any null string values with an empty string COALESCE(string_example,) AS string_example, -- Replace any null numeric values with 0 NVL(number_example,0) AS number_example FROM ext PARTITIONED BY MONTHJSON-based batch原生index_parallel任务通过transformSpec.transforms实现同样效果{ type: index_parallel, spec: { ioConfig: { type: index_parallel, inputSource: { type: inline, data: {\time\:\2024-01-01T00:00:00.000Z\,\string_example\:\my_string\,\number_example\:99}\n{\time\:\2024-01-02T00:00:00.000Z\,\string_example\:\\,\number_example\:0}\n{\time\:\2024-01-03T00:00:00.000Z\,\string_example\:null,\number_example\:null} }, inputFormat: { type: json } }, tuningConfig: { type: index_parallel, partitionsSpec: { type: dynamic } }, dataSchema: { dataSource: inline_data_native, timestampSpec: { column: time, format: iso }, dimensionsSpec: { dimensions: [ string_example, { type: long, name: number_example } ] }, granularitySpec: { queryGranularity: none, rollup: false, segmentGranularity: MONTH }, transformSpec: { transforms: [ { type: expression, name: string_example, expression: COALESCE(\string_example\,) }, { type: expression, name: number_example, expression: NVL(\number_example\,0) } ] } } } }Druid 摄入后的数据不含任何 null|__time|string_examle|number_example| | -- | -- | -- | |2024-01-01T00:00:00.000Z|my_string| 99 | |2024-01-02T00:00:00.000Z|empty| 0 | |2024-01-03T00:00:00.000Z|empty| 0 |从实现上看COALESCE、NVL与case_searched等都是 Druid 表达式引擎内置函数在 Function.java 中定义了case_searchedFunction.java 中NvlFunc继承自CoalesceFunc并注册为nvlFunction.java 中注册了coalesce。表达式函数的语义可由 FunctionTest.java 中的用例验证例如case_searched(xbaz,is baz,xfoo,is foo,is other)在xfoo时返回is foo否则返回默认分支is other。这些函数既可用于摄入 transform也可直接用于查询表达式。路线一B摄入时用 NULLIF 把空字符串强转为 null遗留模式下Druid 在相等比较时把空字符串当作 null。如果查询依赖用空串表示 null可以在摄入阶段用NULLIF把空串强转为 null。例如以下输入数据{time:2024-01-01T00:00:00.000Z,string_example:my_string} {time:2024-01-02T00:00:00.000Z,string_example:} {time:2024-01-03T00:00:00.000Z,string_example:null}在遗留模式下第三条记录会被写成空字符串因此下面的查询返回 2SELECT count(*) FROM null_string WHERE string_example IS NULL而在 SQL 兼容模式下空串与 null 被区分存储同样的查询只会返回 1。下面演示如何把空串强转为 null 以恢复IS NULL比较的语义。SQL-based batchREPLACE INTO null_string OVERWRITE ALL WITH ext AS ( SELECT * FROM TABLE( EXTERN( {type:inline,data:{\time\:\2024-01-01T00:00:00.000Z\,\string_example\:\my_string\}\n{\time\:\2024-01-02T00:00:00.000Z\,\string_example\:\\}\n{\time\:\2024-01-03T00:00:00.000Z\,\string_example\:null}}, {type:json} ) ) EXTEND (time VARCHAR, string_example VARCHAR) ) SELECT TIME_PARSE(time) AS __time, NULLIF(string_example,) AS string_example FROM ext PARTITIONED BY MONTHJSON-based batch注意这里 transform 表达式被展开成了case_searched形式即(string_example )为真时返回 null否则返回原值{ type: index_parallel, spec: { ioConfig: { type: index_parallel, inputSource: { type: inline, data: {\time\:\2024-01-01T00:00:00.000Z\,\string_example\:\my_string\}\n{\time\:\2024-01-02T00:00:00.000Z\,\string_example\:\\}\n{\time\:\2024-01-03T00:00:00.000Z\,\string_example\:null} }, inputFormat: { type: json } }, tuningConfig: { type: index_parallel, partitionsSpec: { type: dynamic } }, dataSchema: { dataSource: null_string, timestampSpec: { column: time, format: iso }, transformSpec: { transforms: [ { type: expression, expression: case_searched((\string_example\ ),null,\string_example\), name: string_example } ] }, dimensionsSpec: { dimensions: [ string_example ] }, granularitySpec: { queryGranularity: none, rollup: false, segmentGranularity: month } } } }Druid 摄入后的数据不含任何空字符串|__time|string_examle| | -- | -- | -- | |2024-01-01T00:00:00.000Z|my_string| |2024-01-02T00:00:00.000Z|null| |2024-01-03T00:00:00.000Z|null|此时SELECT count(*) FROM null_string WHERE string_example IS NULL返回 2与遗留模式行为一致。NULLIF的 SQL 层行为也可以在 CalciteQueryTest.java 的testNullEmptyStringEquality用例中找到佐证测试验证了NULLIF(dim2, a) IS NULL这类过滤条件的翻译结果。路线二重写查询使其符合 ANSI SQL 语义如果需要保留数据中的 null可采用如下 ANSI SQL 兼容的查询改写策略让结果与遗留 null 处理一致改写不等值查询显式包含 null例如x some value改为(x some value OR x IS NULL)。用 COALESCE / NVL 把 null 替换为具体值例如x 1改为NVL(numeric_value, 0)1。考虑以下null_example数据源|__time|string_examle|number_example| | -- | -- | -- | |2024-01-01T00:00:00.000Z|my_string| 99 | |2024-01-02T00:00:00.000Z|empty| 0 | |2024-01-03T00:00:00.000Z|null| null |Druid 会把 null 字符串排除在相等比较之外。例如SELECT COUNT(*) AS count_example FROM null_example WHERE string_example my_stringDruid 返回 1因为 null 被视为 unknown既不等于也不等于该值。要统计 null可以加一个 OR 条件SELECT COUNT(*) AS count_example FROM null_example WHERE (string_example my_string) OR string_example IS NULL此时返回 2。更简洁的做法是使用 null 安全比较IS DISTINCT FROMSELECT COUNT(*) as count_example FROM null_example WHERE string_example IS DISTINCT FROM my_string同理对 null 做算术运算会得到 null。例如SELECT number_example 1 AS additon_example FROM null_example按 ANSI SQL 标准null 与任何值相加仍为 nullDruid 返回|addition_example| | -- | | 100 | | 1 | | null |用NVL可以避免算术中的 null 传播SELECT NVL(number_example,0) 1 AS additon_example FROM null_exampleDruid 返回|addition_example| | -- | | 100 | | 1 | | 1 |注意这里第三条记录从 null 变成了 1NVL(null, 0) 1这与遗留模式下数值 null 按 0 参与运算的语义保持一致。摄入与查询遵循同一套 null 规则SQL 兼容模式下的 null 处理规则同时作用于存储与查询两端。与上述迁移技巧配合还可以直接通过 null 处理教程 中的示例数据集含空字符串与 null 字符串混存的 4 行数据来验证默认模式下的行为例如WHERE string_value ! some_value会排除 null 行COUNT(*)计入所有行而COUNT(string_value)不计入 null 值GROUP BY会把 null 与空串作为两个不同的分组需要同时匹配空串与 null 时用string_value IS NULL OR string_value 需要排除空串时用COUNT(string_value) FILTER(WHERE string_value )数值比较不会计入 null 行numeric_value 1对 null 行返回 null而COALESCE(numeric_value, 0) 1返回 1。这些查询模式本身就是对 SQL 兼容三值逻辑的直观验证也是排查迁移后查询结果差异的有力工具。迁移检查清单综合以上内容从遗留模式迁移到 SQL 兼容模式的完整步骤可以总结为评估依赖排查现有 SQL 查询与原生查询中对空串 / null 相等比较、数值 null 按 0 参与运算的依赖点。选择路线不需要保留 null → 走摄入 transform 替换方案COALESCE/NVL或 NULLIF 强转空串必须保留 null → 走查询重写方案OR IS NULL、IS DISTINCT FROM、NVL/COALESCE。改造摄入使用上面给出的 SQL-based 或 JSON-based 示例在transformSpec中落地替换逻辑并检查段内数据是否符合预期。重写查询将不等值过滤改写为显式包含 null 的写法将算术运算包装为NVL(expr, default)。验证结果用 null 处理教程 中的对照查询IS NULL、COUNT(*)vsCOUNT(expr)、GROUP BY、COALESCE算术逐条核对迁移前后的返回行数与数值。清理配置确认配置文件不再包含druid.generic.useDefaultValueForNull、druid.expressions.useStrictBooleans、druid.generic.useThreeValueLogicForNativeFilters这三个遗留开关的非默认值保持默认 SQL 兼容行为注意这些配置被移除后即使残留也会被忽略。延伸阅读null 处理教程默认 null 处理机制的逐步演示。Null valuesDruid 对 null 值行为的完整描述以及遗留模式与布尔逻辑三值逻辑的配置说明。Handling null valuesDruid 在段存储层面如何保存 null 值的实现细节。【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid6/druid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

compromise 词库数据管线:从 data/ 词表到预训练词模型的压缩与打包机制

compromise 词库数据管线:从 data/ 词表到预训练词模型的压缩与打包机制

NLP人工智能 【免费下载链接】compromise modest natural-language processing 项目地址: https://gitcode.com/gh_mirrors/co/compromise 点击查看 免费下载 导读 本指南聚焦 compromise(modest natural-language processing)项目中负责词…

2026/9/23 15:29:05 阅读更多 →
时域自相关法实现DSSS直扩信号盲估计:码片速率与伪码周期提取

时域自相关法实现DSSS直扩信号盲估计:码片速率与伪码周期提取

简介:面向无线通信方向学生与研究人员的MATLAB代码包,基于时域自相关法实现DSSS/BPSK信号伪码周期估计,用于理解扩频信号处理、参数估计与算法实现流程。包内共8个.m源码文件,涵盖m序列生成、BPSK调制/解调、信号发送及自相关检测…

2026/9/23 15:29:05 阅读更多 →
2026最新神乐千鹤实战:解决代码跑不通的3种调试法

2026最新神乐千鹤实战:解决代码跑不通的3种调试法

2026最新神乐千鹤实战:解决代码跑不通的3种调试法 刚把神乐千鹤的示例代码复制进本地,结果报错?别慌,这在2026最新的开发环境里太常见了。很多老手都栽在这一步:源码看着对,跑起来却像换了个人。 一、 为什么复制来的代码总是“水土不服”…

2026/9/23 15:29:05 阅读更多 →

最新新闻

对象存储加密怎么把密钥握在自己手里:安当TDE的密钥自管

对象存储加密怎么把密钥握在自己手里:安当TDE的密钥自管

一、对象存储的"加密错觉" 企业把海量文件、备份、日志、图片丢进对象存储(私有云自建或公有云 OSS),控制台点一下"开启服务端加密",就以为安全了。但大多数默认加密是云厂商托管密钥——密钥在云厂商手里&am…

2026/9/23 16:19:13 阅读更多 →
YOLOv11工业质检实战:小缺陷检测与产线节拍优化

YOLOv11工业质检实战:小缺陷检测与产线节拍优化

简介:这份PDF文档面向工业质检领域的技术开发人员与算法工程师,系统讲解基于YOLOv11的高精度缺陷检测与实时分类解决方案。内容从工业质检背景与挑战切入,梳理YOLO系列算法演进及YOLOv11的骨干网络、颈部网络与检测头结构,进而展开…

2026/9/23 16:19:13 阅读更多 →
智慧校园无感人脸识别考勤查寝系统:从抓拍到轨迹查询的落地实践

智慧校园无感人脸识别考勤查寝系统:从抓拍到轨迹查询的落地实践

简介:这份PDF方案面向学校安全管理者、宿管教师及智慧校园系统集成人员,针对传统人工查寝效率低、学生轨迹信息缺失、宿舍外来人员管控不到位等痛点,给出无感人脸识别考勤查寝的完整解决思路。资源包共1个PDF文件,约294KB&#xf…

2026/9/23 16:19:13 阅读更多 →
告别低效入网许可证校验:一份性能优化的速查手册

告别低效入网许可证校验:一份性能优化的速查手册

告别低效入网许可证校验:一份性能优化的速查手册 看了一堆教程还是不会写项目?别急,问题往往出在细节的耗时上。很多人以为业务逻辑写完就能跑,结果上线后因为 入网许可证 的重复校验和数据库高频查询,系统响应慢得像蜗牛。这篇 速查手册…

2026/9/23 16:19:13 阅读更多 →
Cadence Genus综合模板构建与iSpatial物理感知实践

Cadence Genus综合模板构建与iSpatial物理感知实践

简介:本资源是一份面向数字IC前端设计工程师与EDA工具使用者的Genus综合平台技术精讲资料,聚焦Cadence新一代iSpatial Flow物理综合流程,解决传统前后端分离导致的时序预测不准、迭代次数多、PPA优化受限等核心痛点。资料以PDF形式呈现&#…

2026/9/23 16:19:12 阅读更多 →
Numba CUDA Python 参考指南:从 Host API 到 Kernel 内建函数与 Libdevice 完整 API 索引

Numba CUDA Python 参考指南:从 Host API 到 Kernel 内建函数与 Libdevice 完整 API 索引

Numba CUDA Python 参考指南:从 Host API 到 Kernel 内建函数与 Libdevice 完整 API 索引 【免费下载链接】numba NumPy aware dynamic Python compiler using LLVM 项目地址: https://gitcode.com/gh_mirrors/nu/numba Numba 的 docs/source/cuda-reference…

2026/9/23 16:18:10 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →