DuckDB 能改 JSON 了:4 个新函数对齐 SQLite
DuckDB 读 JSON 是一把好手。read_json() 直接吞文件,-> 和 ->> 提取字段,json_extract() 走 JSONPath。但有一个事它一直做不了——改 JSON。
你没法在 DuckDB 里给 JSON 加个字段、删个 key、替换某个嵌套值。这些事你得把数据导出去,用 Python 或 jq 处理完再导回来。
今天这个缺口补上了。PR #23786 合并进了主分支,四个新标量函数上线:
| 函数 | 行为 |
|---|---|
json_set(doc, path, value) | 在 path 设置 value。路径不存在就创建,存在就覆盖 |
json_insert(doc, path, value) | 在 path 插入 value。如果已经有值,什么也不做 |
json_replace(doc, path, value) | 替换 path 处的值。如果路径不存在,什么也不做 |
json_remove(doc, path) | 删除 path 处的值。路径不存在则无操作 |
作者是 mustafahasankhan,经过 DuckDB 核心开发者 lnkuiper(Laurens Kuiper)review 后合并。改动量不小:json_modify.cpp 新增 330 行实现,外加 1,161 行测试覆盖。
● ● ●
怎么用
四个函数的用法跟 SQLite 一模一样,名字和语义都是对齐的:
-- 给 JSON 加字段
SELECT json_set('{"name": "DuckDB"}', '$.version', '1.5.4');
-- {"name":"DuckDB","version":"1.5.4"}
-- insert 不会覆盖已有值
SELECT json_insert('{"name": "DuckDB"}', '$.name', 'SQLite');
-- {"name":"DuckDB"} ← 没变,因为 name 已经存在
-- replace 只在路径存在时才生效
SELECT json_replace('{"name": "DuckDB"}', '$.bogus', 'nope');
-- {"name":"DuckDB"} ← 没变,$.bogus 不存在
-- remove 删字段
SELECT json_remove('{"name": "DuckDB", "temp": 42}', '$.temp');
-- {"name":"DuckDB"}
更实用的场景:批量清洗 JSON 列。
-- 去掉所有行的 debug 字段 UPDATE events SET payload = json_remove(payload, '$.debug') WHERE json_type(payload, '$.debug') IS NOT NULL; -- 给所有行加上处理时间戳 UPDATE events SET payload = json_set(payload, '$.processed_at', now());
如果你用 PostgreSQL,这些函数对应 jsonb_set() 那套 API。现在 DuckDB 也能在 SQL 层直接处理 JSON 修改了。
● ● ●
对齐 SQLite,不是 PostgreSQL
一个有意思的设计选择:团队选了 SQLite 的语义,而不是 PostgreSQL 的。
review 过程中有一个细节很说明问题。json_set 创建缺失路径时,如果路径里有数字下标(比如 $.a[0]),应该创建数组还是对象?第一版实现创建了对象 {"a":{"0":1}},但 SQLite 创建的是 {"a":[1]}。lnkuiper 直接给了 SQLite 的 repro 让改过去,mustafahasankhan 又专门写了完整矩阵,逐条对齐 SQLite 3.43 的行为:
- 只有 index 0 在新数组里是合法的
- 标量永不替换
- root 的类型永不转换
- 越界 index 触发 NOP
这跟 DuckDB 一贯的定位一致——嵌入式分析数据库,不是 PostgreSQL 替代品。对齐 SQLite 意味着你本地用 SQLite 开发的应用,换 DuckDB 做大查询时 JSON 行为一致,不用改逻辑。
● ● ●
这意味着什么
DuckDB 在补"通用 SQL 能力"这件事上越来越认真了。之前有 json_extract、json_each、JSONPath 支持,现在加了修改能力,JSON 这块基本完整。
而且这不是社区扩展——代码在 extension/json/ 下,但随主仓一起编译。JSON 是一等公民,不是外包给社区的活。
对于实际工作流,这意味着你可以:
- ETL 清洗:直接在 DuckDB 里修改 JSON 列,不用先导出再用 Python 处理
- 日志处理:读进 JSON 日志→去敏感字段→写入 Parquet,一条 SQL 走完
- API 响应处理:拿到 API 返回的 JSON,用
json_remove删掉不关心的嵌套,再用read_json拆成表格
以前这套流程至少需要一次"DuckDB → Python → DuckDB"的跳转,现在纯 SQL 就行。
PR 链接:https://github.com/duckdb/duckdb/pull/23786
DuckDB 在变的越来越不像一个"纯分析数据库"了。JSON 修改函数又是这个方向上的一步。
你平时用 DuckDB 处理 JSON 数据吗?遇到过需要改 JSON 却被迫导出再处理的场景吗?评论区聊聊。
更多 DuckDB 动态:
github.com/duckdb/duckdb
duckdb.org/news