痛点:批量读取 .duckdb 文件太繁琐
假设你在做一个月度数据分析项目,有 100 个按月份分片的 .duckdb 数据库文件,每个文件里都有一个名为 events 的表。现在你需要把它们合并在一起做汇总分析。
传统做法是什么?
ATTACH 'data/2024-01.duckdb' AS db1;
ATTACH 'data/2024-02.duckdb' AS db2;
ATTACH 'data/2024-03.duckdb' AS db3;
-- ... 还有 97 个文件要手动写
SELECT * FROM db1.events
UNION ALL
SELECT * FROM db2.events
UNION ALL
SELECT * FROM db3.events;
-- ... 97 个 UNION ALL
100 个文件 → 200 行 SQL。这不仅痛苦,而且无法动态扩展——每个月新增文件都要改代码。
一招解决:read_duckdb() + Globbing
DuckDB v1.5.0 引入了 read_duckdb() 表函数,它可以直接读取 .duckdb 文件中的表,无需 ATTACH:
SELECT * FROM read_duckdb('data/*.duckdb');
就这么一行。支持 glob 通配符,自动扫描所有匹配的文件,自动合并结果。
更实用的写法:指定具体表
如果每个 .duckdb 文件里有多个表,你可以用 table_name 参数精确指定:
SELECT * FROM read_duckdb(
'data/*.duckdb',
table_name => 'events'
);
自动追踪数据来源
想知道每行数据来自哪个文件?read_duckdb() 会自动添加 _source_file 列,方便溯源:
SELECT
_source_file,
COUNT(*) AS event_count,
SUM(amount) AS total_amount
FROM read_duckdb('data/2024-*.duckdb', table_name => 'events')
GROUP BY _source_file
ORDER BY event_count DESC;
输出示例:
_source_file | event_count | total_amount
-----------------------+-------------+--------------
data/2024-03.duckdb | 152340 | 4892341.50
data/2024-01.duckdb | 148920 | 4723102.80
data/2024-02.duckdb | 151200 | 4812456.20
Python 中使用
在 Python 中也一样简洁:
import duckdb
# 一行代码读取所有 .duckdb 文件的 events 表
df = duckdb.query("""
SELECT * FROM read_duckdb(
'/data/monthly_reports/*.duckdb',
table_name => 'events'
)
""").df()
print(f"共读取 {len(df)} 行数据")
效果量化
| 维度 | ATTACH + UNION ALL | read_duckdb() |
|---|---|---|
| 代码行数 | 200+ 行(100 个文件) | 3 行 |
| 新增文件适配 | 手动修改 SQL | 零改动,自动生效 |
| 可读性 | ❌ 难以维护 | ✅ 一目了然 |
| 错误率 | 高(容易漏写/拼错) | 极低 |
| 执行时间 | 相近(底层优化一致) | 相近 |
核心收益:将 200+ 行手工 SQL 压缩到 3 行声明式查询,且新增文件零成本。
进阶用法
嵌套读取:从 read_duckdb 的结果再做聚合
SELECT
DATE_TRUNC('month', event_time) AS month,
COUNT(*) AS total_events,
AVG(amount) AS avg_amount
FROM read_duckdb('data/2024-*.duckdb', table_name => 'events')
GROUP BY month
ORDER BY month;
配合 FILTER 做数据质量检查
SELECT
_source_file,
COUNT(*) FILTER (WHERE amount IS NULL) AS null_amounts,
COUNT(*) FILTER (WHERE event_type = '') AS empty_types
FROM read_duckdb('data/*.duckdb', table_name => 'events')
GROUP BY _source_file
HAVING null_amounts > 0 OR empty_types > 0;
这个查询可以在 10 毫秒内找出所有存在数据质量问题的文件——比逐文件打开检查快几个数量级。
递归子目录
SELECT * FROM read_duckdb('data/**/*.duckdb', table_name => 'events');
** 支持递归匹配子目录,适合层级存储的场景。
延伸思考
read_duckdb() 的设计哲学是 “让 DuckDB 能读写自己的文件格式”。这带来了几个有趣的场景:
- 分布式数据处理:每个节点本地写一个
.duckdb文件,汇总时用read_duckdb('output/*.duckdb')一键合并。 - 备份即数据:你的
.duckdb数据库文件本身就是备份,随时可以用read_duckdb()读取任意时间点的数据。 - 插件化架构:微服务各自维护独立的
.duckdb文件,跨服务分析时直接 glob 读取,无需共享数据库实例。
这个功能在 DuckDB v1.5.0 引入,支持 late materialization 和 filter pushdown,意味着即使读取几百个文件,优化器也会把过滤条件推到每个文件中执行,只读取真正需要的数据。
📬 Subscribe to DuckDB Lab for weekly practical tips that you can use immediately. No fluff, just actionable DuckDB tricks.