DuckDB 宏(Macros)完全指南:把重复 SQL 片段打包成可复用函数
TL;DR:DuckDB 的
CREATE MACRO让你把反复出现的 SQL 逻辑封装成带参数的函数,一行调用替代整段 CTE。适合报表模板、通用计算、跨项目复用。本文从基础到实战,覆盖参数默认值、结构化返回、与视图的对比,以及如何用宏搭建可变现的数据产品。

一、为什么需要宏?
你在写 SQL 时是否遇到过这些场景:
- 每个月都要写同样的"工作日计算"逻辑,复制粘贴了 5 次
- 某个复杂的日期处理表达式散布在 10 个查询里,改一处要动 10 个文件
- 团队里每个人都有自己的"工具 CTE",版本不一致经常出 bug
传统解决方案是用视图(VIEW),但视图有两个致命缺陷:
- 不能带参数——你没法写
SELECT * FROM get_weekly_report('2026-08-01') - 不能组合表达式——视图只能返回整个表,不能返回一个计算值
DuckDB 的 宏(Macro) 就是为了解决这两个问题而生的。
二、基础用法:一行定义,随处调用
2.1 最简单的宏
-- 定义:计算两个日期之间的工作日天数
CREATE MACRO workdays(start_date, end_date) AS
(end_date::DATE - start_date::DATE + 1)
- (date_part('dow', start_date::DATE) + date_part('dow', end_date::DATE));
-- 调用
SELECT workdays('2026-08-10', '2026-08-17') AS result;
-- 返回:5(去掉了周六周日)
注意:宏体是一个表达式,不是完整的 SELECT 语句。这让宏可以嵌入到任何 SQL 表达式的位置。
2.2 多表达式宏
-- 定义:判断是否为节假日(简化版)
CREATE MACRO is_holiday(date_val) AS
CASE date_val::DATE
WHEN '2026-01-01' THEN true
WHEN '2026-05-01' THEN true
WHEN '2026-10-01' THEN true
ELSE false
END;
-- 在查询中使用
SELECT
order_date,
amount,
is_holiday(order_date) AS is_holiday_flag
FROM orders
WHERE is_holiday(order_date);
三、高级用法:默认参数与结构化返回
3.1 带默认值的参数
-- 安全除法:除数为 0 时返回默认值
CREATE MACRO safe_divide(a, b, default_val := 0) AS
CASE WHEN b = 0 THEN default_val ELSE a / b END;
SELECT
safe_divide(10, 3) AS normal, -- 3.333...
safe_divide(10, 0) AS zero; -- 0
默认参数让你在不传所有参数的情况下也能调用宏,非常灵活。
3.2 返回结构化数据(struct)
-- 把日期拆成年/月/日
CREATE MACRO parse_date(d) AS
struct_pack(
year := extract('year' FROM d),
month := extract('month' FROM d),
day := extract('day' FROM d)
);
SELECT parse_date('2026-08-13'::DATE);
-- 返回:{'year': 2026, 'month': 8, 'day': 13}
-- 解构使用
SELECT
(parse_date(order_date)).year AS order_year,
(parse_date(order_date)).month AS order_month,
SUM(amount) AS total
FROM orders
GROUP BY order_year, order_month;
3.3 列表宏(LIST 返回)
-- 生成 N 个连续日期
CREATE MACRO date_range(start, count) AS
generate_series(start, start + INTERVAL (count - 1) DAY, INTERVAL 1 DAY);
SELECT date_range('2026-08-01', 7);
-- 返回:[2026-08-01, 2026-08-02, ..., 2026-08-07]
四、宏 vs 视图 vs CTE:怎么选?
| 特性 | 宏 (MACRO) | 视图 (VIEW) | CTE |
|---|---|---|---|
| 带参数 | ✅ | ❌ | ❌ |
| 返回标量值 | ✅ | ❌ | ❌ |
| 返回表 | ❌ | ✅ | ✅ |
| 跨会话持久 | ✅(临时)/ ✅(持久) | ✅ | ❌ |
| 代码复用 | ✅ | ✅ | ❌ |
| 性能优化 | 内联展开 | 物化可选 | 每次重算 |
选择原则:
- 需要参数化的表达式 → 用宏
- 需要返回整张表且被多处引用 → 用视图
- 单次查询内的临时逻辑 → 用 CTE
- 需要跨会话持久化的复杂查询 → 用物化视图
五、实战场景:三个马上能用的宏
5.1 金额格式化宏
CREATE MACRO fmt_money(val, currency := '¥') AS
currency || ROUND(val, 2);
SELECT fmt_money(1234.5), fmt_money(567.8, '$');
-- 返回:¥1234.50 | $567.80
5.2 同比环比计算宏
CREATE MACRO calc_mom(current_val, prev_val) AS
ROUND(100.0 * (current_val - prev_val) / NULLIF(prev_val, 0), 2);
-- 在分析查询中使用
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
calc_mom(revenue, LAG(revenue) OVER (ORDER BY month)) AS mom_pct
FROM monthly_sales;
5.3 数据质量检查宏
CREATE MACRO check_null_ratio(table_ref, col_name) AS
ROUND(
100.0 * SUM(CASE WHEN {col_name} IS NULL THEN 1 ELSE 0 END)
/ COUNT(*),
2
);
-- 使用:检查orders表的user_id列空值率
SELECT check_null_ratio(orders, user_id) AS null_ratio_pct;
六、与传统工具的对比
| 工具 | 参数化 | 表达式复用 | 学习成本 | 适用场景 |
|---|---|---|---|---|
| DuckDB 宏 | ✅ | ✅ | 低 | 嵌入式分析、SQL 优先场景 |
| Python 函数 | ✅ | ✅ | 中 | 复杂逻辑、需要调用外部库 |
| SQL 视图 | ❌ | ✅ | 低 | 返回固定表结构 |
| Excel 公式 | ✅ | ❌ | 低 | 小规模数据、非技术人员 |
| 存储过程 | ✅ | ✅ | 高 | 传统数据库、复杂事务 |
核心优势:宏让你在不离开 SQL 的情况下获得函数式编程的能力,无需切换语言上下文。
七、变现建议:宏能帮你赚多少钱?
7.1 个人提效 → 时间变现
把你在项目中重复写了 10 次的 SQL 逻辑封装成宏库,后续项目直接复用。假设你每周节省 3 小时,时薪 ¥200,每年多赚 ¥31,200。
7.2 宏库产品化
把你的常用宏打包成开源库(GitHub),吸引开发者使用。可以通过以下方式变现:
- GitHub Sponsors:每月 ¥100-500 赞助
- 付费宏库:在 Gumroad 或小报童上售卖高级宏包(¥99-299)
- 培训课程:录制"SQL 高级技巧"课程,宏是核心章节
7.3 企业级数据产品
用宏构建可配置的报表模板,面向中小企业收费:
| 产品 | 定价 | 目标客户 |
|---|---|---|
| 财务报表宏包 | ¥99/月 | 中小企业财务 |
| 电商分析宏包 | ¥199/月 | 电商运营 |
| 自定义宏开发 | ¥500-2000/次 | 定制需求客户 |
7.4 技术博客引流
写宏的实战教程发布到 duckdblab.org,通过 SEO 吸引搜索"DuckDB 宏"的用户。一篇高质量教程每月可带来 500-2000 独立访客,转化为付费用户后月增收 ¥500-5000。
八、总结
DuckDB 的宏功能是一个被严重低估的特性。它填补了"视图不能带参数"和"CTE 无法跨查询复用"之间的空白,让你用纯 SQL 就能写出可复用、可配置的计算逻辑。
核心要点:
- 宏是表达式级别的复用,不是表级别的
- 支持默认参数,调用更灵活
- 可以返回标量、struct、list 等多种类型
- 比视图更适合参数化场景,比 CTE 更适合跨查询复用
下次当你发现自己复制粘贴同一段 SQL 超过 3 次时,停下来想想:这能不能变成一个宏?
📖 更多 DuckDB 宏实战案例 → duckdblab.org