DuckDB 在本地跑 SQL 分析 Parquet(无服务器、零安装、列存极速)

DuckDB 是嵌入式分析数据库("SQLite for analytics"),单二进制,
能直接读 Parquet / CSV / JSON 文件,复杂分析查询比 pandas / Spark
本地模式快得多。

适合:本地数据探索、报表生成、数据科学家"我有 50GB Parquet 在 S3
想跑几条 SQL"。

安装

uv add duckdb
# 或独立 CLI:
curl https://install.duckdb.org | sh
duckdb --version

CLI 入门

duckdb my.db
# duckdb shell
D SELECT 1 + 1;
┌───────┐
│ ?col0 │
├───────┤
│   2   │
└───────┘

D .help
D .quit

直接查 Parquet(不需要导入)

-- 单文件
SELECT * FROM 'data/sales.parquet' LIMIT 10;

-- 多文件
SELECT * FROM 'data/year=*/month=*/*.parquet' LIMIT 10;

-- S3 直接读
SET s3_region='us-east-1';
SET s3_access_key_id='...';
SET s3_secret_access_key='...';
SELECT count(*) FROM 's3://bucket/data/*.parquet';

year=*/month=* 是 Hive 风格分区路径,DuckDB 自动识别并 prune 不需要
的分区。

与 polars / pandas 互通

import duckdb
import polars as pl

# 直接查 polars DataFrame(不复制数据,Arrow zero-copy)
df = pl.read_csv('users.csv')
result = duckdb.sql("SELECT country, COUNT(*) FROM df GROUP BY 1").pl()

# 同理 pandas
import pandas as pd
pdf = pd.DataFrame({...})
result = duckdb.sql("SELECT ... FROM pdf").df()

duckdb.sql(...) 返回 DuckDBPyRelation,可以 .pl() .df() .fetchall()
转换。

典型用例

1. 探索一份大 CSV

import duckdb

duckdb.sql("""
    SELECT
      country,
      COUNT(*) AS rows,
      AVG(amount) AS avg_amt,
      QUANTILE_CONT(amount, [0.5, 0.95, 0.99]) AS p50_p95_p99
    FROM 'sales.csv'
    WHERE date >= '2024-01-01'
    GROUP BY country
    ORDER BY rows DESC
    LIMIT 20
""").show()

整个 CSV 在内存里只读一次,DuckDB 自动用所有 CPU 核心。

2. CSV → Parquet 转换

duckdb.sql("""
    COPY (SELECT * FROM 'sales.csv') TO 'sales.parquet' (FORMAT PARQUET)
""")
# 同样一份数据,Parquet 通常 1/3 大小 + 列查询快 10x

按字段分区:

duckdb.sql("""
    COPY (SELECT * FROM 'sales.csv')
    TO 'partitioned/'
    (FORMAT PARQUET, PARTITION_BY (year, month))
""")
# 输出:partitioned/year=2024/month=01/data_0.parquet ...

3. JOIN 多个文件

SELECT u.name, COUNT(*) AS n
FROM 'users.parquet' u
JOIN 'orders.parquet' o ON o.user_id = u.id
WHERE o.date >= '2024-01-01'
GROUP BY u.name
ORDER BY n DESC
LIMIT 10;

DuckDB 优化器自己决定 hash join / merge join;不需要建索引。

4. 窗口函数

SELECT
  user_id,
  date,
  amount,
  SUM(amount) OVER (PARTITION BY user_id ORDER BY date) AS cum_amount,
  ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn
FROM 'orders.parquet'
WHERE rn = 1;

完整 SQL:2003 窗口函数。

5. 写回数据库

# 写到 Postgres
duckdb.sql("INSTALL postgres; LOAD postgres;")
duckdb.sql("""
    ATTACH 'host=localhost dbname=mydb' AS pg (TYPE POSTGRES);
    COPY (SELECT * FROM 'data.parquet') TO pg.public.my_table;
""")

DuckDB 与 SQLite 对比

维度 SQLite DuckDB
设计 OLTP(事务) OLAP(分析)
存储 行存 列存
多核 单线程 自动并行
Parquet 不直接支持 一等公民
文件 .db 单文件 .db 单文件,也能完全无文件

不要替代——它们解决不同问题。事务用 SQLite,分析用 DuckDB。

持久化 vs 内存

import duckdb

# 内存(默认)
con = duckdb.connect(':memory:')

# 文件
con = duckdb.connect('analytics.db')

文件模式可以 CREATE TABLE 持久化数据。内存模式纯查询。

大数据场景:spilling to disk

DuckDB 内存不够时自动 spill 到 tmp/

duckdb.sql("PRAGMA temp_directory='./tmp_duckdb';")
duckdb.sql("PRAGMA memory_limit='4GB';")

跑大 query 不会 OOM,但慢点。比起 Spark 集群部署成本,单机能跑通是
胜利。

与 BI 工具集成

# Tableau / DBeaver / Metabase 都有 DuckDB connector
# JDBC: org.duckdb.DuckDBDriver
# ODBC: 官方提供

数据科学家用 DuckDB 算分析,BI 工具直接连展示。

CLI 高级技巧

# 用 fzf 选 .parquet 文件查
duckdb -c "SELECT * FROM '$(fd -e parquet | fzf)' LIMIT 100"

# 输出 JSON
duckdb -json -c "SELECT * FROM 'data.parquet' LIMIT 5"

# 输出 CSV
duckdb -c "COPY (SELECT * FROM 'data.parquet' WHERE x > 0) TO STDOUT WITH (HEADER, FORMAT CSV)"

踩过的坑

  • 写 Parquet 时 string column 含 NULL → 默认 NULL handling 可能不对。
    COPY ... TO ... (FORMAT PARQUET, COMPRESSION 'zstd') 显式指定参数。
  • S3 大量小文件(< 10MB / 文件)→ 元数据请求开销大。聚合成少量大文件
    再扫。
  • DuckDB 跨大版本(0.x → 1.x)数据库文件格式有时不兼容;升级前
    EXPORT DATABASE '...' 备份。
  • python 包默认装的是 prebuilt wheel,没装某些 extension(spatial /
    excel)。INSTALL spatial; LOAD spatial; 运行时装。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。