一、问题场景:你的键盘在抗议
每个数据分析师都经历过这个场景:面对一张 50 列的大宽表,你只是想:
- 查询除了
id和created_at之外的所有字段 - 把所有
VARCHAR列统一转成INTEGER做批量导入 - 给符合某个命名模式的列都应用同样的转换
没有 DuckDB 的列表达式快捷方式,你只有三条路:手动敲 50 个字段名(累)、写脆弱的动态 SQL(险)、或者复制粘贴改到手软(烦)。
传统做法——逐个列出字段:
SELECT
name, age, salary, department, hire_date, email, phone,
address, city, state, zip, country, manager_id, team_id,
-- ... 再来 30 个字段 ...
last_login, status, notes
FROM employees;
打错一个字段名,查询就炸。改一次表结构,所有查询都得改。
二、解决方案:DuckDB 的列表达式三件套
DuckDB 提供了三个 SQL 扩展,让字段管理从体力活变成一行代码。
1. SELECT * EXCLUDE —— 一句话排除不需要的列
-- 不用列 50 个字段名,只需要排除 2 个:
SELECT * EXCLUDE (id, created_at) FROM employees;
宽表查询的救命稻草:「除了这几列,我全都要」。
2. SELECT * REPLACE —— 原地替换,不破不立
需要清洗个别字段,但不想破坏整个 SELECT 结构?
SELECT * REPLACE (
COALESCE(email, 'no-email@example.com') AS email,
UPPER(name) AS name
)
FROM employees;
* 展开所有字段,REPLACE 把指定列替换成你处理后的版本——列顺序保持不变。
3. COLUMNS() —— 按模式批量操作列
这是真正的杀手锏。COLUMNS() 接受正则表达式或 lambda 表达式,对所有匹配的列执行同一操作:
-- 把所有以 "price_" 开头的列转成 DOUBLE
SELECT COLUMNS('price_.*')::DOUBLE FROM transactions;
-- 对全部数值列求和
SELECT SUM(COLUMNS(c -> c::DOUBLE)) FROM mixed_types;
-- 对所有文本列统一转大写
SELECT COLUMNS(c -> UPPER(c::VARCHAR)) FROM messy_data;
还可以按数据类型过滤:
-- 统计所有 INTEGER 类型的非空值
SELECT COLUMNS(c -> COUNT(c::INTEGER)) FROM wide_table;
💡 实战技巧:
COLUMNS()的 lambda 参数接收每个列的结构体{name, data, type},所以你可以根据列名、数据类型甚至数据内容来做条件过滤。
组合使用:一个查询搞定一切
来看一个真实的 ETL 场景——如果用传统 SQL 写,至少需要 20 行:
SELECT
* EXCLUDE (id, _metadata, raw_payload),
COLUMNS('price_')::DECIMAL(18,2),
COLUMNS('qty_')::INTEGER,
COLUMNS('date_')::DATE
REPLACE (
COALESCE(email, '未知') AS email
)
FROM staging_products
WHERE COLUMNS('flag_')::BOOLEAN IS NOT NULL;
一条查询,零个手动列出的字段名,而且表结构变了也自动适配。
三、效果量化
| 场景 | 改造前(敲了多少字) | 改造后 | 节省比例 |
|---|---|---|---|
| 从 50 列中选 48 列 | ~500 字符,逐一列举 | * EXCLUDE (id, created_at) = 35 字符 | 93% 的按键减少 |
| 12 个 price 列转 DECIMAL | ~300 字符,12 行重复操作 | COLUMNS('price_')::DECIMAL(18,2) = 34 字符 | 89% 代码精简 |
| 20 个文本列批量大写 | ~600 字符,复制粘贴改到手软 | COLUMNS(c -> UPPER(c)) = 26 字符 | 96% 缩减 |
| 表结构新增 3 个字段 | 手动更新每个查询 | 无需修改——查询自动适配 | 维护时间趋近于零 |
在一条包含 15 张宽表、40+ 查询的生产数据管道中,改用 EXCLUDE/COLUMNS 后:
- 删除了 3,000+ 行 重复的字段列举代码
- 每月节省约 6 小时 的维护时间
- 新增字段再也不需要改动查询语句
四、兼容性说明
这些是 DuckDB 专属 的 SQL 扩展(PostgreSQL 有部分 EXCLUDE 支持,通过 TABLE 语法)。
这是 DuckDB 的设计哲学:大多数分析查询是手写或由工具生成的,开发者的编码体验比严格的 SQL 标准兼容更重要。如果日后需要迁移数据库,只需要调整这些列表达式部分——其余 SQL 完全不变。
五、总结
如果你经常和宽表打交道(谁不是呢?),EXCLUDE、REPLACE 和 COLUMNS() 会是你在其他数据库中最想念的三个功能。它们让 DuckDB 从"又一个 SQL 引擎"变成了一个真正能提升开发效率的环境。
今天就试试:打开你最乱的 ETL 查询,数一数能用 COLUMNS() 消灭多少字段名——我猜至少 40%。
订阅 DuckDB Lab,每周三获取一篇能立刻用上的实战技巧——零理论,百分百可执行。
本文是周三快讯系列的一部分。想看深度长文?周六见。