引言
你在工作中是否遇到过这样的场景:用户的完整信息散落在多个系统中——HR 系统有姓名和部门,考勤系统有出勤天数,CRM 有消费记录,数据仓库又有行为标签。这些系统的字段不完全重叠,有的字段名相同但含义不同,有的系统甚至把整个档案存成了一个 JSON 字符串。
传统的做法是用 Python 写一堆循环,逐个取字段、处理冲突、拼新对象。代码冗长,维护困难,一旦上游字段变化就要改一堆逻辑。
DuckDB 提供了 json_merge_patch 函数,专门解决这个问题。它遵循 RFC 7396 标准,核心逻辑很简单:后到的值覆盖先到的值,缺失的字段自动补全。一行 SQL 就能完成多源 JSON 的智能合并。
一、json_merge_patch 基础用法
最简单的合并
SELECT json_merge_patch(
'{"name":"张三","age":30,"email":"[email protected]"}',
'{"age":31,"phone":"13800138000"}'
) AS merged_result;
输出结果:
{"name":"张三","age":31,"email":"[email protected]","phone":"13800138000"}
第二条记录只传了 age 和 phone 两个字段,但合并后原有数据全部保留。age 被新值覆盖(30 → 31),phone 自动补上,name 和 email 原样保留。
理解合并逻辑
json_merge_patch(a, b) 的工作方式:
- 如果键在
a和b中都存在,b 的值覆盖 a 的值 - 如果键只在
a中存在,保留 a 的值 - 如果键只在
b中存在,从 b 取新值 - 如果值是嵌套对象,递归合并,而不是整体覆盖
二、实战场景 1:用户档案合并
假设你是一家 SaaS 公司的数据工程师,需要把三个系统的数据合并成统一的客户档案。
数据源
| 系统 | JSON 内容 | 关键字段 |
|---|---|---|
| CRM 系统 | {"customer_id":"C001","name":"李四","source":"sales_team"} | 客户基本信息 |
| 业务系统 | {"customer_id":"C001","total_orders":15,"total_spend":28400} | 交易数据 |
| 标签系统 | {"customer_id":"C001","segment":"vip","churn_risk":"low"} | 用户画像 |
DuckDB 合并查询
import duckdb
con = duckdb.connect(':memory:')
# 三个数据源
crm_json = '{"customer_id":"C001","name":"李四","source":"sales_team"}'
biz_json = '{"customer_id":"C001","total_orders":15,"total_spend":28400}'
tag_json = '{"customer_id":"C001","segment":"vip","churn_risk":"low"}'
# 核心合并:两两合并,递归处理
result = con.execute(f"""
SELECT json_merge_patch(
json_merge_patch('{crm_json}', '{biz_json}'),
'{tag_json}'
) AS full_profile
""").fetchone()
print(result[0])
# {"customer_id":"C001","name":"李四","source":"sales_team",
# "total_orders":15,"total_spend":28400,
# "segment":"vip","churn_risk":"low"}
关键点:
customer_id在所有表中都存在,后面的覆盖前面的——但值相同,所以没问题- 各表独有的字段(
name、total_orders、segment)全部保留 - 三层嵌套也可以处理:如果某张表的值是嵌套对象,会自动递归合并
三、实战场景 2:从表数据合并
实际工作中,数据通常已经在表里了。假设你有两张表,分别存储同一用户的基础信息和扩展信息:
-- 创建测试数据
CREATE TABLE user_basic (
user_id VARCHAR,
profile_json JSON
);
CREATE TABLE user_extra (
user_id VARCHAR,
extra_json JSON
);
-- 插入数据
INSERT INTO user_basic VALUES
('U001', '{"name":"王五","age":28,"city":"北京"}'),
('U002', '{"name":"赵六","age":35,"city":"上海"}'),
('U003', '{"name":"钱七","age":22,"city":"广州"}');
INSERT INTO user_extra VALUES
('U001', '{"membership":"gold","points":15000,"join_date":"2023-01-15"}'),
('U002', '{"membership":"silver","points":8000,"join_date":"2022-06-20"}'),
('U003', '{"membership":"bronze","points":2000,"join_date":"2024-03-01"}');
合并查询非常简洁:
SELECT
a.user_id,
json_merge_patch(a.profile_json, b.extra_json) AS full_profile
FROM user_basic a
JOIN user_extra b ON a.user_id = b.user_id;
结果:
| user_id | full_profile |
|---|---|
| U001 | {"name":"王五","age":28,"city":"北京","membership":"gold","points":15000,"join_date":"2023-01-15"} |
| U002 | {"name":"赵六","age":35,"city":"上海","membership":"silver","points":8000,"join_date":"2022-06-20"} |
| U003 | {"name":"钱七","age":22,"city":"广州","membership":"bronze","points":2000,"join_date":"2024-03-01"} |
四、实战场景 3:日志数据提取 + 合并
在处理 API 日志或系统事件时,经常需要从原始 JSON 中提取关键字段,然后合并到一个标准格式里。
import duckdb
con = duckdb.connect(':memory:')
# 模拟多条 API 日志
logs = [
'{"timestamp":"2026-09-10T10:00:00Z","level":"ERROR","message":"Connection timeout","service":"api-gateway"}',
'{"timestamp":"2026-09-10T10:05:00Z","level":"WARN","message":"High memory usage","service":"data-pipeline","memory_pct":87}',
'{"timestamp":"2026-09-10T10:10:00Z","level":"INFO","message":"Deployment complete","service":"deploy-service","version":"2.3.1"}',
]
# 从每条日志中提取关键字段,合并成标准格式
result = con.execute(f"""
SELECT
json_extract('{logs[0]}', '$.level') AS level,
json_extract('{logs[0]}', '$.message') AS message,
json_extract('{logs[0]}', '$.service') AS service,
json_merge_patch(
'{{"type":"log"}}',
'{logs[0]}'
) AS standardized
""").fetchone()
print(result[0:3]) # ('ERROR', 'Connection timeout', 'api-gateway')
print(result[3]) # 完整标准化后的 JSON
这个技巧特别适合做数据标准化管道——不同来源的日志格式各异,用 json_merge_patch 加上一个默认模板,就能统一成规范格式。
五、进阶:嵌套对象的递归合并
json_merge_patch 最强大的地方在于递归合并。当两个 JSON 对象中有嵌套对象时,不会整体覆盖,而是深入一层再合并。
SELECT json_merge_patch(
'{"user":{"name":"张三","preferences":{"theme":"dark","lang":"zh"}}}',
'{"user":{"preferences":{"notifications":true}}}'
) AS merged;
结果:
{
"user": {
"name": "张三",
"preferences": {
"theme": "dark",
"lang": "zh",
"notifications": true
}
}
}
注意 preferences 没有丢失 theme 和 lang,notifications 是新加入的字段。这就是递归合并的价值——你可以只更新部分配置,而不必重写整个对象。
六、性能对比:DuckDB vs Python
很多人第一反应是用 Python 处理这种需求,我们来对比一下:
# Python 方案
import json
profile_a = {"name": "张三", "age": 30, "email": "[email protected]"}
profile_b = {"age": 31, "phone": "13800138000"}
profile_c = {"segment": "vip", "churn_risk": "low"}
# 手动合并,需要处理冲突和缺失
def deep_merge(base, update):
result = base.copy()
for key, value in update.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
merged = deep_merge(deep_merge(profile_a, profile_b), profile_c)
对比表:
| 维度 | Python 方案 | DuckDB json_merge_patch |
|---|---|---|
| 代码量 | 15+ 行(含递归函数) | 1 行 SQL |
| 可读性 | 需理解自定义 merge 逻辑 | 语义清晰,即见即所得 |
| 嵌入 ETL | 需先导出再导入 | 直接在 SQL 管线中执行 |
| 批量处理 | 循环遍历,性能差 | DuckDB 并行执行,速度极快 |
| 嵌套合并 | 需自己实现递归 | 内置递归合并 |
关键区别在于批量处理:当面对百万行用户数据时,Python 循环会非常慢,而 DuckDB 利用向量化执行,合并操作几乎不增加额外开销。
七、DuckDB v2.0 的 JSON 升级预告
DuckDB v2.0(代号 Cyanoptera)将在今年秋天发布,带来了 4 个全新的 JSON 函数:
| 函数 | 作用 | 使用场景 |
|---|---|---|
json_merge_patch_diff | 计算两个 JSON 的差异补丁 | 版本控制、变更追踪 |
json_deep_merge | 深度合并,null 值跳过不覆盖 | 配置合并(不希望 null 覆盖有效值) |
json_normalize | 规范化 JSON 键顺序 | 数据去重和指纹比较 |
json_strip_nulls | 递归去除 null 值 | 数据清洗 |
json_deep_merge 尤其值得注意——标准的 json_merge_patch 中,如果第二个参数的值是 null,它会覆盖第一个参数的值。但有时候我们希望 null 表示"不更新",而不是"清空"。json_deep_merge 就是为这个场景设计的。
八、变现建议:把这个能力变成产品
掌握了 json_merge_patch 这个能力,你可以开发以下付费产品或服务:
1. 数据对齐 SaaS
企业的数据散落在 ERP、CRM、OA 等多个系统中,每次报表都需要手动对齐。你把 DuckDB + json_merge_patch 封装成一个在线工具,用户上传多份 CSV/JSON 文件,自动合并输出。月费制,99~499 元/月。
2. 自动化 ETL 管线
很多中小企业没有数据工程师,但又有数据整合需求。你用 DuckDB 为他们搭建自动化的 ETL 管线——每天定时从多个数据源拉取数据,用 json_merge_patch 合并后写入目标库。按项目收费,单个客户 3000~10000 元。
3. 数据质量报告服务
结合 v2.0 的 json_merge_patch_diff,你可以提供数据变更追踪服务。告诉客户"A 系统和 B 系统的用户数据有 3 个字段不一致",这种诊断服务本身就可以收费。
4. 日志标准化中间件
API 网关收到的日志格式各异,用 DuckDB 做实时标准化,输出统一格式给下游分析系统。这是一个典型的中间件场景,可以做成微服务出售。
总结
json_merge_patch 是 DuckDB 处理多源 JSON 数据的核心武器。它用一行 SQL 替代了传统 Python 中的大量拼接逻辑,支持递归合并嵌套对象,可以轻松嵌入 ETL 管线。
当你下次面对分散在多个系统中的用户数据时,先想想:是不是 json_merge_patch 就能搞定?
📖 更多 DuckDB JSON 实战技巧 → duckdblab.org
