
为什么你需要 JSON 补丁函数?
想象你在构建一个数据中台。系统 A 每分钟产生 10 万条用户变更记录,系统 B 同步更新用户画像。你需要比对两边的数据,找出差异并应用变更。
在 DuckDB v2.0 之前,这类操作需要在 SQL 之外完成——用 Python 或 Java 编写复杂的 JSON 处理逻辑。现在,DuckDB v2.0 引入了四大 JSON 补丁函数,让你直接在 SQL 中完成整个数据对账流程。
DuckDB v2.0 四大 JSON 补丁函数详解
1. json_merge_patch_diff:计算最小增量补丁
json_merge_patch_diff(orig, modified) 返回一个 RFC 7396 格式的补丁,使得 json_merge_patch(orig, patch) = modified。
-- 基本用法
SELECT json_merge_patch_diff(
'{"a":1,"b":2,"c":3}',
'{"a":1,"b":99,"d":4}'
) AS patch;
-- 结果: {"c":null,"b":99,"d":4}
-- a 未变所以省略,b 已修改,c 被删除(标记为 null),d 是新字段
递归处理嵌套对象:
SELECT json_merge_patch_diff(
'{"user":{"name":"Alice","age":30}}',
'{"user":{"name":"Alice","age":31}}'
) AS patch;
-- 结果: {"user":{"age":31}}
-- 只有变更的路径才会出现在补丁中
这在 CDC(变更数据捕获)管道中特别有用——大多数事件只改变几十个字段中的一个或两个。发送补丁而不是完整状态,可以将变更载荷压缩到原来的很小一部分。
2. json_deep_merge:递归合并,null 表示"跳过"
与 json_merge_patch 不同(RFC 7396 规定 null 删除键),json_deep_merge 的 null 值表示"保留原始值"。
-- json_merge_patch: null 删除键
SELECT json_merge_patch('{"a":1,"b":2}', '{"b":null}');
-- 结果: {"a":1}
-- json_deep_merge: null 保留原值
SELECT json_deep_merge('{"a":1,"b":2}', '{"b":null}');
-- 结果: {"a":1,"b":2}
多源数据融合的典型场景:
-- 两个上游系统各自只更新部分字段
SELECT json_deep_merge(
'{"columnName":"user_id","parentColumn":null}',
'{"columnName":null,"parentColumn":"accounts.id"}'
) AS merged;
-- 结果: {"columnName":"user_id","parentColumn":"accounts.id"}
-- 每个系统不知道的值用 null 表示,deep_merge 会保留原始值
支持可变参数,多个补丁从左到右依次应用:
SELECT json_deep_merge(
'{"a":1}',
'{"a":null}', -- 跳过
'{"a":2}' -- 覆盖
);
-- 结果: {"a":2}
3. json_normalize:键顺序规范化
当两个服务以不同键顺序发出相同的 JSON 对象时,如何判断它们是否相同?json_normalize 递归排序所有对象的键,数组元素保持原有顺序。
SELECT json_normalize('{"z":1,"a":2,"m":3}');
-- 结果: {"a":2,"m":3,"z":1}
SELECT json_normalize('{"c":{"b":{"z":1,"a":2},"a":3},"a":4}');
-- 结果: {"a":4,"c":{"a":3,"b":{"a":2,"z":1}}}
-- 用于去重:相同内容但不同键顺序的 JSON 会产生相同的 hash
SELECT md5(json_normalize('{"z":1,"a":2}')) =
md5(json_normalize('{"a":2,"z":1}')) AS same_hash;
-- 结果: true
4. json_strip_nulls:递归移除空值
SELECT json_strip_nulls('{"a":1,"b":null,"c":null,"d":2}');
-- 结果: {"a":1,"d":2}
SELECT json_strip_nulls('{"a":{"x":1,"y":null},"b":2}');
-- 结果: {"a":{"x":1},"b":2}
-- 数组元素不受影响(null 是有效的数组元素)
SELECT json_strip_nulls('{"a":[{"x":1,"y":null},{"z":null}]}');
-- 结果: {"a":[{"x":1,"y":null},{}]}
端到端 CDC 对账实战
假设你的上游系统在每个变更事件中标记"未知字段"为 null,目标是:清理事件、计算最小补丁、存储规范化哈希。
-- 创建表
CREATE TABLE catalog (id INTEGER, state JSON);
CREATE TABLE incoming_events (id INTEGER, event JSON);
-- 插入当前状态
INSERT INTO catalog VALUES (
1,
'{"typeName":"Column","name":"user_id","description":"primary key",
"dataType":"BIGINT","ownerEmail":"[email protected]"}'
);
-- 插入 incoming 事件(部分字段为 null)
INSERT INTO incoming_events VALUES (
1,
'{"name":"user_id","description":"primary key for the users table",
"dataType":"BIGINT","ownerEmail":null,"team":null}'
);
-- 完整对账流程(单条 SQL)
WITH cleaned AS (
-- 步骤1:清理 null 占位符
SELECT json_strip_nulls(event) AS clean_event
FROM incoming_events
WHERE id = 1
),
merged AS (
-- 步骤2:深度合并(缺失字段保留原值)
SELECT json_deep_merge(c.state, cl.clean_event) AS merged_state
FROM catalog c, cleaned cl
),
patched AS (
-- 步骤3:计算最小补丁
SELECT json_merge_patch_diff(c.state, m.merged_state) AS patch
FROM catalog c, merged m
)
-- 步骤4:应用补丁并计算规范化哈希
SELECT
patch,
md5(json_normalize(json_merge_patch(c.state, p.patch))) AS content_hash
FROM catalog c, patched p
WHERE c.id = 1;
结果分析:
| 字段 | 说明 |
|---|---|
patch | {"description":"primary key for the users table"} — 只有变更字段 |
content_hash | 规范化后的内容哈希,用于去重 |
dataType 未变所以不出现在补丁中,ownerEmail 和 team 被清理后也不出现。最终补丁只包含真正变更的字段。
性能对比:DuckDB vs Python
DuckDB 官方博客在 50 万条 CDC 事件上进行了基准测试:
| 函数 | Python (秒) | DuckDB (秒) | 加速比 |
|---|---|---|---|
json_normalize | 7.02 | 0.15 | 46.8× |
json_deep_merge | 8.45 | 0.47 | 18.0× |
json_merge_patch_diff | 6.66 | 0.19 | 35.1× |
json_strip_nulls | 6.17 | 0.05 | 123.4× |
| 完整链式调用 | 11.11 | 1.03 | 10.8× |
性能优势来源:
- DuckDB 直接在 yyjson 树上进行原地操作,避免 Python 的
json.loads/json.dumps开销 - 向量化执行模式,每行处理 2048 个值
- 多线程并行处理
与传统工具对比
| 特性 | DuckDB v2.0 JSON 补丁 | Python (jsonpatch) | Spark | Snowflake |
|---|---|---|---|---|
| 增量补丁计算 | ✅ json_merge_patch_diff | ❌ 需自定义 | ❌ | ❌ |
| 智能合并(null 跳过) | ✅ json_deep_merge | ❌ | ❌ | ❌ |
| 键顺序规范化 | ✅ json_normalize | ❌ | ❌ | ❌ |
| 空值清理 | ✅ json_strip_nulls | ❌ | ❌ | ❌ |
| 端到端 SQL 流程 | ✅ | ⚠️ 需 Python UDF | ⚠️ | ⚠️ |
| 50万条性能 | 1.03秒 | 11.11秒 | 需集群 | 需集群 |
| 部署成本 | 单机即可 | 单机即可 | 高 | 高 |
如何在 v1.5.x 中提前体验
虽然 v2.0 正式版尚未发布,但你可以通过以下方式提前测试:
# 安装 DuckDB v2.0-dev preview
pip install duckdb --pre
import duckdb
con = duckdb.connect(":memory:")
con.execute("LOAD json;")
# 测试 json_normalize
result = con.execute("SELECT json_normalize('{\"z\":1,\"a\":2}');").fetchone()
print(result) # ('{"a":2,"z":1}',)
变现建议:用 JSON 对账技能赚钱
1. 数据对账 SaaS 服务
目标客户: 需要多系统数据同步的企业(电商、金融、SaaS)
商业模式:
- 按对账记录数收费:$0.001/条
- 月订阅制:$299-$999/月
- 企业定制:$5000+/月
核心卖点:
- 实时对账,秒级发现数据不一致
- 自动计算最小补丁,减少下游同步量
- 原生 SQL 接口,无需编写代码
技术架构:
上游系统A → CDC → DuckDB (json_deep_merge + patch) → 下游系统B
上游系统C → CDC → DuckDB (json_merge_patch_diff) → 差异化报告
2. 数据质量监控服务
目标客户: 数据中台团队、数据工程师
产品形式:
- 自助式数据质量仪表盘
- 自动对账报告生成
- 异常变更实时告警
定价:
- 基础版:$99/月(10万条/月)
- 专业版:$499/月(100万条/月)
- 企业版:定制定价
3. 数据迁移工具
场景: 企业从 MongoDB/文档数据库迁移到关系型数据库
流程:
- 用
json_normalize统一不同来源的文档格式 - 用
json_strip_nulls清理无效数据 - 用
json_deep_merge合并多版本文档 - 批量写入目标表
定价: 按迁移数据量收费,$0.01/MB
4. 技术咨询与培训
服务内容:
- 企业 CDC 管道架构设计
- DuckDB JSON 函数性能优化
- 数据对账最佳实践培训
定价:
- 咨询:$200-$500/小时
- 企业培训:$5000-$20000/天
总结
DuckDB v2.0 的四大 JSON 补丁函数解决了数据对账领域的长期痛点。通过 json_merge_patch_diff、json_deep_merge、json_normalize 和 json_strip_nulls 的组合,你可以:
- 在 SQL 中完成原本需要 Python 的代码
- 性能提升 10-123 倍
- 构建实时数据对账 SaaS 产品
这些函数不仅适用于 CDC 管道,还可以用于 API 响应规范化、配置管理、版本控制等多种场景。现在就开始在 v2.0-dev 中体验吧!