Featured image of post 用 json_merge_patch 合并多源数据:DuckDB 一行 SQL 搞定数据对齐

用 json_merge_patch 合并多源数据:DuckDB 一行 SQL 搞定数据对齐

同一用户的资料分散在 HR 系统、考勤系统、CRM 等多个数据源,字段重叠但不一致。本文教你用 DuckDB 的 json_merge_patch 函数,一行 SQL 完成多源 JSON 数据的智能合并,替代传统 Python 循环处理方案。

引言

你在工作中是否遇到过这样的场景:用户的完整信息散落在多个系统中——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"}

第二条记录只传了 agephone 两个字段,但合并后原有数据全部保留。age 被新值覆盖(30 → 31),phone 自动补上,nameemail 原样保留。

理解合并逻辑

json_merge_patch(a, b) 的工作方式:

  • 如果键在 ab 中都存在,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 在所有表中都存在,后面的覆盖前面的——但值相同,所以没问题
  • 各表独有的字段(nametotal_orderssegment)全部保留
  • 三层嵌套也可以处理:如果某张表的值是嵌套对象,会自动递归合并

三、实战场景 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_idfull_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 没有丢失 themelangnotifications 是新加入的字段。这就是递归合并的价值——你可以只更新部分配置,而不必重写整个对象。


六、性能对比: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

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。