浏览器里直接跑 DuckDB SQL:分析 Parquet / CSV / Excel 不用装环境

· 约 5 分钟 🦆 DuckDB SQL 工作台

数据分析师每天要处理一堆”陌生文件”——同事甩过来的 .parquet、客户给的 .xlsx、抓取的 .csv / .ndjson。传统流程:开 Jupyter → 装 pandas + pyarrow → pd.read_parquet() → 查列名 → 写 groupby → 看结果。装环境就 5 分钟,数据量稍大 pandas 还会 OOM。

浏览器里跑 DuckDB SQL 是这两年才能做到的方案:DuckDB 团队把整个 C++ 引擎编译成 WebAssembly(约 35 MB wasm),分析师拖文件进网页就能写 SQL,结果毫秒级返回,不需要安装、不需要后端、文件不离开本地。

浏览器内 OLAP 引擎是什么

DuckDB 是过去几年最火的「单机分析数据库」,核心特点是:

  • 列存 + 向量化执行:聚合查询比 SQLite / Postgres 快几十倍
  • 零部署:单文件二进制(CLI)或单文件 wasm(浏览器),不需要 server / 配置
  • 格式无感:直接 SQL 查 Parquet / CSV / JSON,不需要先 LOAD INTO 一张表
  • PostgreSQL 兼容语法:CTE / 窗口函数 / QUALIFY / GROUP BY ALL / lambda 都支持

把它编译成 wasm 之后,浏览器就成了一个本地 OLAP 节点。和把 SQLite 编进 wasm 不同的是——SQLite 面向 OLTP(小事务、行存),DuckDB 面向 OLAP(大查询、列存),后者才是「数据分析」该用的数据库。

5 个实战场景

1. 看 Parquet 文件里有什么

拿到一个陌生 Parquet 想知道列、类型、行数:

-- 列结构 + 类型
DESCRIBE big.parquet;

-- 行数和样本
SELECT COUNT(*) AS rows FROM big.parquet;
SELECT * FROM big.parquet LIMIT 10;

工具自动跑了第三句,但前两句你可以手动追加补充。DESCRIBE 是 DuckDB 特有语法,比 PRAGMA table_info 输出更友好。

2. CSV 当成数据库查(替代 Excel 透视表)

老板甩了一份 sales.csv 想知道各区域销售:

-- 等价于 Excel 透视表「区域 → 月 → 总销售额」
SELECT
  region,
  date_trunc('month', sale_date) AS month,
  SUM(amount) AS gmv,
  COUNT(*) AS orders,
  AVG(amount)::DECIMAL(10,2) AS aov
FROM sales
GROUP BY ALL
ORDER BY region, month;

GROUP BY ALL 是 DuckDB / Snowflake 的便利语法——自动按所有非聚合列分组,省了把 SELECT 列表的前几列再写一遍。Excel 透视表点 10 下能搞定的活,SQL 4 行写完,而且重复使用、可改、可版本控制。

3. 多文件 JOIN(跨数据源关联)

events.parquet + users.csv + categories.json

SELECT
  u.country,
  c.name AS category,
  COUNT(*) AS events,
  COUNT(DISTINCT e.user_id) AS uv
FROM events e
JOIN users u ON e.user_id = u.id
JOIN categories c ON u.country_category_id = c.id
WHERE e.event_name = 'purchase'
  AND e.ts >= '2026-01-01'
GROUP BY 1, 2
QUALIFY ROW_NUMBER() OVER (PARTITION BY u.country ORDER BY uv DESC) <= 3;

QUALIFY 是窗口函数版本的 WHERE——在窗口计算之后过滤,省掉一层子查询。这种”每个国家 Top 3 类目”的查询是分析师高频场景,用 SQL 几行解决,pandas 写起来要套一坨 groupby + apply + nlargest

4. Excel 多 sheet 跨表分析

拖入 sales.xlsx(Q1 + Q2 双 sheet):

-- 跨 sheet UNION + 同比
WITH all_sales AS (
  SELECT 'Q1' AS quarter, * FROM sales_q1
  UNION ALL
  SELECT 'Q2', * FROM sales_q2
)
SELECT
  region,
  SUM(total) FILTER (WHERE quarter = 'Q1') AS q1_gmv,
  SUM(total) FILTER (WHERE quarter = 'Q2') AS q2_gmv,
  ROUND((SUM(total) FILTER (WHERE quarter = 'Q2'))
        / NULLIF(SUM(total) FILTER (WHERE quarter = 'Q1'), 0) * 100 - 100, 1) AS qoq_pct
FROM all_sales
GROUP BY region
ORDER BY q2_gmv DESC;

FILTER (WHERE ...) 是 SQL 标准的「条件聚合」,比 SUM(CASE WHEN ... THEN x ELSE 0 END) 更可读。Excel 里要做这种环比要建额外的辅助列 + VLOOKUP,SQL 一段搞定。

5. 数据质量自检(异常值 / 空值 / 重复)

-- 缺失率
SELECT
  COUNT(*) AS total,
  COUNT(*) - COUNT(email) AS email_missing,
  COUNT(*) - COUNT(phone) AS phone_missing,
  ROUND((1.0 - COUNT(email)::DOUBLE / COUNT(*)) * 100, 2) AS email_missing_pct
FROM users;

-- 重复主键
SELECT id, COUNT(*) AS dup_count
FROM users
GROUP BY id
HAVING COUNT(*) > 1;

-- 数值异常
SELECT
  MIN(amount) AS min_v,
  MAX(amount) AS max_v,
  AVG(amount) AS avg_v,
  STDDEV(amount) AS sd,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median,
  PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY amount) AS p99
FROM sales;

数据交付前的”sanity check”——每条 30 秒跑完,比 Excel 一列列点筛选快得多。

Parquet vs CSV vs Excel:选什么

维度ParquetCSVExcel (xlsx)
体积★★★★★(列存 + 压缩)★★★★★(zip 内是 XML)
类型★★★★★(schema 内嵌)★(要嗅探)★★★★(cell type)
跨语言★★★★★★★★★★★★★
浏览器解析速度★★★★★★★★★★★(zip 解压慢)
给非技术同事看★(要工具)★★★★★★★★★
大文件友好★★★★★★★★(行数限制 + 性能)

建议

  • 数据存储 / 数据交付(工程间):Parquet(小、快、类型准)
  • 临时给同事看:CSV(任何人都能打开)
  • 运营 / 财务交付:Excel(带格式、能直接看)
  • 本工具的 sweet spot:拿到任意三种之一,浏览器直接 SQL 分析,省去格式转换

性能边界

场景表现
启动加载(首次)35 MB wasm 下载 + 实例化,~5-10 秒(看带宽)
Parquet 100 万行加载 1-3 秒,聚合查询 < 200 ms
CSV 50 万行加载 + 推断类型 1-2 秒,查询 < 100 ms
Excel 10 万行 30 列SheetJS 解析 + 转 CSV 30-60 秒(Worker 内不卡 UI),之后查询毫秒级
极限单文件 1-2 GB,超过会受 V8 堆限制(2-4 GB)

V8 单页堆 OOM 时浏览器会刷新页面,所以重要的查询结果记得及时导出

同款”先看一眼”工具

只想瞄一眼 Parquet 文件的 schema 和前 N 行、不需要写 SQL,用更轻量的 Parquet 在线预览(基于 hyparquet,包体积 50 KB,秒开)。两个工具配合:预览器看结构 → SQL 工具做分析,覆盖整条「看一眼 → 探索 → 输出」链路。

局限

  • 不持久化:刷新丢编辑器内容、丢已注册的表(这是本地优先 + 隐私设计的取舍)
  • 不联网读外部 URL:DuckDB 的 httpfs 扩展默认未启用(要联网就跟”本地隐私”冲突)
  • 大于 V8 堆的文件:去用本地 DuckDB CLI,相同 SQL 直接复制能跑
  • 复杂特征工程:要调用 Python 生态(sklearn / numpy)的还是 Jupyter

❓ 常见问题

DuckDB-Wasm 真的是完整的 DuckDB 吗?

,跟命令行的 duckdb 同源同语法。官方仓库 @duckdb/duckdb-wasm 把 DuckDB 的 C++ 引擎用 Emscripten 编译成 WebAssembly,跑在浏览器 Worker 里。常用 OLAP 特性都在:CTE、窗口函数、QUALIFYGROUP BY ALLLIST_AGG、JSON -> 操作符、lambda、UNNEST、Parquet 列裁剪和谓词下推。和 CLI 的差异主要是:(1) 文件 IO 走浏览器虚拟文件系统不是真磁盘;(2) 部分 Postgres 兼容的系统视图省了;(3) 扩展(如 httpfsspatial)需要在 wasm 端单独加载。日常分析 99% 的语法直接互通。

文件会上传到哪里?

不上传。文件用 db.registerFileBuffer() 注册到 DuckDB 内部的 VFS(虚拟文件系统),引擎读浏览器内存里的字节,跟传统 ODBC/JDBC 连接的概念完全不同。可以断网用,可以打开 DevTools Network 面板验证。适合处理敏感数据:未脱敏的客户日志、内部财务表、未公开的训练集都不会离开你的电脑。35 MB 左右的 wasm 引擎本身从同源静态资源加载一次,之后浏览器缓存。

pandas / Polars / Excel 已经能干这事,为什么还要在浏览器里跑?

省安装。临时拿到一个 Parquet 想看一眼分布——开 Jupyter 装 pandas + pyarrow 至少 5 分钟,本工具拖文件 3 秒就能查询。给非工程同事用。运营/PM 没有 Python 环境,但浏览器都有;把工具链接发过去他们就能跑预定义的 SQL。隐私场景。客户的 .xlsx 不能传到云端 SaaS,本地装 DuckDB CLI 又嫌重——浏览器版正好。注意:超大文件(> 2 GB)和需要复杂依赖(如调用 Python 库做特征工程)的场景,依然该用 pandas/Polars/CLI 版 DuckDB。

Excel 文件怎么处理?真能跑 SQL?

走转换路径:xlsx 拖进来后由 SheetJS 在 Worker 里把每个非空 sheet 解析成 CSV bytes,再用 CTAS 物化进 DuckDB 基础表(所以 UPDATE/INSERT/DELETE 直接可用)。多 sheet 工作簿 → 每个 sheet 各成一张表,命名 <文件名>_<sheet名>,可以跨 sheet JOIN。类型推断 → DuckDB 的 read_csv_auto 嗅探,日期列由 SheetJS 输出 ISO 字符串后再被 DuckDB 解析。性能 → 100 K 行 30 列的 xlsx 解析约 30-60 秒(SheetJS 的 zip 解压 + 单元格解析在 Worker 里跑,主线程不卡),之后 SQL 查询毫秒级。

Parquet 比 CSV 好在哪?

列存 + 压缩。CSV 是行存文本,10 GB CSV 在 Parquet 里通常 1-2 GB(Snappy/ZSTD 压缩 + 字典编码 + RLE)。类型自带。Parquet 文件的 schema 内嵌(INT64、TIMESTAMP、DECIMAL 等),不会有"读 CSV 把 ID 推断成 string"的坑。列裁剪SELECT a, b FROM big.parquet 只读这两列对应的字节范围,不需要扫描全文件——CSV 必须从头读到尾。谓词下推WHERE date > "2026-01-01" 在 Parquet 里能利用 row group 的统计信息直接跳过整段。实务:数据写出去要长期存就 Parquet,临时给非技术同事看就 CSV。

写完的 SQL 怎么保存 / 复用?

当前版本不持久化——刷新页面 SQL 编辑器内容会丢。临时绕路:(1) 复制 SQL 文本贴到本地笔记 / Notion;(2) 复制查询结果到 Excel 或 Sheets;(3) 用浏览器扩展(如 OneTab / Pin tab)保留页面状态——localStorage 持久化的 SQL 编辑器内容是后续要加的功能。复用思路:把工具当成"一次性查询面板"——做一次性数据探索(看分布、跑 ad-hoc JOIN、找异常值)很合适;要做长期固化的报表还是写到 dbt / Airflow / 笔记本里去。

几 GB 的 Parquet 能跑吗?

1-2 GB 比较舒服。DuckDB-Wasm 跑在浏览器主线程的 Worker 里,受 V8 单页堆约束(一般 2-4 GB)。Parquet 因为列存,SELECT col1, col2 FROM big GROUP BY 1 只解码这两列,比 CSV 友好得多——同等数据 10 GB 的 CSV 跑不动,但 1-2 GB 的 Parquet 通常没问题。实战建议:先 LIMIT 10 / WHERE 缩小范围再聚合,让 DuckDB 的列裁剪和谓词下推帮你省内存。真要分析 10 GB+ 数据:用本地 DuckDB CLI(同样的 SQL 直接复制粘贴跑),它能用真磁盘做 spill。

跨文件 JOIN 怎么写?

和普通数据库一模一样。比如同时拖入 events.parquetusers.csv,两张表名分别是 eventsusers,直接:SELECT u.country, COUNT(*) FROM events e JOIN users u ON e.user_id = u.id GROUP BY 1 ORDER BY 2 DESC列名补全——SQL 编辑器在输入 表名. 后会弹出该表的所有列。类型不一致——比如 events 的 user_id 是 INT64,users.csv 的 id 被推断成 INT32,需要 e.user_id::INT = u.id 显式转换;DuckDB 的报错信息会直接告诉你哪两列类型不对应。

🦆 打开 DuckDB SQL 工作台 拖入 Parquet/CSV/JSON/Excel→SQL 查询/修改·多文件 JOIN·导出 Parquet/CSV/JSON/Excel·DuckDB-Wasm 浏览器内运行

📖 同一工具的其他教程