DuckDB :一款文件分析引擎
在日常的数据分析工作中,我们经常会陷入一种尴尬的境地:手头攒着几份 CSV、Excel 或 Parquet 文件,只想快速探查一下数据、做个简单的分组聚合。此时,特意去搭建一套重量级的数据库服务(如 MYSQL)未免大材小用;而如果直接用 Pandas,又免不了要写一堆读取、转换、合并的样板代码,略显繁琐。
DuckDB 正是为了解决这种“轻量、快速、即席分析”的痛点而生的。
简单来说,DuckDB 是一款嵌入式的、专注于分析型(OLAP)查询的列式数据库。

它最核心的魅力,在于打破了“数据必须先导入数据库才能查”的传统逻辑,允许你直接用 SQL 去查本地文件。
它最适合解决的是“我手里已经有几份 CSV 和 Excel,只想先查清楚答案”这类问题。先把文件当表查,结果对了,再决定要不要进更重的数据处理流程。
它和 SQLite 也不一样。SQLite 更像通用嵌入式数据库,DuckDB 更偏分析查询,对聚合、关联和列式处理更顺手。

许多本地数据任务,实际上并不需要一上来就构建繁琐的数据处理流程。
手里有几份 CSV,想按区域汇总一下销售额;旁边还有一份退款表,想找出退款订单;库存信息在 Excel 里,想筛出低库存商品。
这些活如果直接上 pandas,当然能做。
但在实际操作中,为了获得一个简单的汇总结果,开发者往往需要编写大量读取文件、转换类型、合并表格及筛选字段的样板代码。
DuckDB 适合换一种方式。
把本地 CSV 和 Excel 当成临时数据表,直接用 SQL 查。
不需要搭建复杂的数据库环境,也无需将数据导入 SQLite,甚至不用将处理步骤拆解为繁杂的 DataFrame。你只需要明确查询逻辑,用 SQL 获取结果,再决定是否进行后续数据加工。

举个例子
我们有一些数据放在:
.cache/topic-139-duckdb-query-files/里面有三份数据:
data/orders.csv 订单表data/refunds.csv 退款表data/inventory.xlsx 库存表我们要完成三件事:
1. 按区域汇总已付款订单金额 2. 把订单表和退款表关联起来,找出退款订单 3. 从 Excel 库存表里筛出低库存商品
跑通后,终端输出大概是这样:
各区域已付款销售额 华东 | 1348.4 | 3 华南 | 625.0 | 2 华北 | 128.0 | 1退款订单 1003 | 北城工作室 | 520.0 1005 | 西岭设计 | 0.0项目已经验证过:
PYTHONPATH=. pytest -q结果是:
3 passed
安装依赖与准备工作
这个示例只需要三个依赖:
duckdb>=1.4.0openpyxl>=3.1.0pytest>=8.0.0
可以这样准备环境:
python -m venv .venv. .venv/bin/activatepip install -r requirements.txtExcel 样例文件由脚本生成:
python scripts/create_sample_xlsx.py如果仅需对 CSV 文件进行查询,可以跳过 Excel 相关配置:
python query_files.py --skip-excel基于 SQL 执行单表 CSV 查询
DuckDB 的 Python 用法很简单:
import duckdbconnection = duckdb.connect(database=":memory:")按区域汇总销售额的函数是这样:
def sales_by_region(connection: duckdb.DuckDBPyConnection) -> list[tuple[str, float, int]]: query = """ SELECT region, round(sum(amount), 2) AS total_amount, count(*) AS order_count FROM read_csv($orders, header = true) WHERE status = 'paid' GROUP BY region ORDER BY total_amount DESC """ return connection.execute(query, {"orders": str(DATA_DIR / "orders.csv")}).fetchall()这段代码里有两个点很实用。
第一,read_csv($orders, header = true) 直接读取 CSV 文件,省去了将数据导入临时表的步骤。
第二,文件路径通过参数传进去,不把路径直接拼进 SQL 字符串里。
SQL 本身也很清楚:
• WHERE status = 'paid'只看已付款订单• GROUP BY region按区域分组• sum(amount)计算销售额• count(*)统计订单数
如果对 SQL 比较熟悉,这种方式比编写一长串链式的 DataFrame 接口更加直观。
跨文件 CSV 关联查询设计
本地文件查询最常见的需求,是把两张表串起来。
比如订单表里有客户和订单金额,退款表里有退款原因和退款金额。想找出退款订单,不需要在本地将两份 CSV 手动合并。
直接 JOIN:
def refunded_orders(connection: duckdb.DuckDBPyConnection) -> list[tuple[int, str, float]]: query = """ SELECT orders.order_id, orders.customer, refunds.refund_amount FROM read_csv($orders, header = true) AS orders JOIN read_csv($refunds, header = true) AS refunds USING (order_id) ORDER BY refunds.refund_amount DESC """ return connection.execute( query, { "orders": str(DATA_DIR / "orders.csv"), "refunds": str(DATA_DIR / "refunds.csv"), }, ).fetchall()这就是 DuckDB 适合本地文件任务的地方。
文件还是文件,但查询时可以像数据库表一样使用。
如果只是临时排查问题,跑一条 SQL 就够了。后面如果要变成长期工具,再把 SQL 固定进脚本和测试。
使用 read_xlsx 查询 Excel 文件
DuckDB 也可以查 Excel。
不过,Excel 读取依赖内置的 excel 扩展。需要在脚本中进行安装与加载:
connection.execute("INSTALL excel")connection.execute("LOAD excel")然后用 read_xlsx 读取指定 sheet:
def low_stock_items(connection: duckdb.DuckDBPyConnection) -> list[tuple[str, str, int, int]]: connection.execute("INSTALL excel") connection.execute("LOAD excel") query = """ SELECT sku, name, stock, safe_stock FROM read_xlsx($inventory, sheet = 'stock') WHERE stock < safe_stock ORDER BY stock ASC """ return connection.execute(query, {"inventory": str(DATA_DIR / "inventory.xlsx")}).fetchall()这段查询做的事很简单:
• 从 inventory.xlsx的stocksheet 读取库存• 找出 stock < safe_stock的商品• 按当前库存从低到高排序
需要注意一点:INSTALL excel 可能需要能访问 DuckDB 的扩展仓库。
如果目标机器处于离线环境或网络受限,应当提前部署扩展,或者将 Excel 转换为 CSV。在编写工具时,应避免盲目假定网络扩展随时可用。
DuckDB 的适用边界
在以下场景中,推荐优先选用 DuckDB:
• 文件是 CSV、Excel、Parquet 这类表格数据 • 目标是筛选、分组、排序、关联、汇总 • 知道自己想查什么数据
• 后面可能会扩展成脚本、报告或自动化流程
如果只是打开表格改几个单元格,Excel 本身就够了。
如果要做复杂数据深加工,不妨试试DuckDB,说不定能帮助我们节省大量时间。
夜雨聆风