痛点:传统时序分析的效率瓶颈
在数据分析领域,时序数据(Time Series Data)无处不在,从销售额、用户活跃度到服务器监控指标。但对时序数据进行复杂的分析,比如计算日环比、周同比、连续增长天数或滑动平均值,常常伴随着以下挑战:
- 复杂的 SQL 语句: 传统上,你可能需要编写多个子查询(Subquery)或使用自连接(Self-Join)来获取历史数据,导致 SQL 语句冗长、难以理解。
- 性能瓶颈: 大量子查询和自连接会导致数据多次扫描和中间结果集的生成,尤其在处理海量时序数据时,查询性能会急剧下降。
- 临时表依赖: 为了简化逻辑或提升性能,分析师有时会创建临时表来存储中间计算结果,增加了数据处理流程的复杂性。
DuckDB 作为一款高性能的 OLAP 数据库,提供了强大的窗口函数(Window Functions)和 QUALIFY 子句,能够完美解决这些痛点,让你以更简洁、高效的方式进行时序分析。
DuckDB 解决方案:QUALIFY 与窗口函数的强强联合
窗口函数允许你在与当前行相关的“窗口”内执行计算,而 QUALIFY 子句则能像 WHERE 子句过滤聚合函数一样,对窗口函数的结果进行过滤。当两者结合时,就能爆发出惊人的分析能力。
数据准备
首先,我们创建一个简单的 sales 表,用于模拟销售数据:
CREATE TABLE sales (
sale_date DATE,
product_id INTEGER,
amount DECIMAL(10, 2)
);
INSERT INTO sales VALUES
('2026-10-01', 101, 100.00),
('2026-10-01', 102, 150.00),
('2026-10-02', 101, 120.00),
('2026-10-02', 102, 160.00),
('2026-10-03', 101, 110.00),
('2026-10-03', 102, 170.00),
('2026-10-04', 101, 130.00),
('2026-10-04', 102, 180.00),
('2026-10-05', 101, 140.00),
('2026-10-05', 102, 190.00),
('2026-10-06', 101, 105.00),
('2026-10-06', 102, 155.00),
('2026-10-07', 101, 115.00),
('2026-10-07', 102, 165.00),
('2026-10-08', 101, 200.00),
('2026-10-08', 102, 250.00);
初始数据如下:
| sale_date | product_id | amount |
|------------|-----------:|-------:|
| 2026-10-01 | 101 | 100.00 |
| 2026-10-01 | 102 | 150.00 |
| 2026-10-02 | 101 | 120.00 |
| 2026-10-02 | 102 | 160.00 |
| 2026-10-03 | 101 | 110.00 |
| 2026-10-03 | 102 | 170.00 |
| 2026-10-04 | 101 | 130.00 |
| 2026-10-04 | 102 | 180.00 |
| 2026-10-05 | 101 | 140.00 |
| 2026-10-05 | 102 | 190.00 |
| 2026-10-06 | 101 | 105.00 |
| 2026-10-06 | 102 | 155.00 |
| 2026-10-07 | 101 | 115.00 |
| 2026-10-07 | 102 | 165.00 |
| 2026-10-08 | 101 | 200.00 |
| 2026-10-08 | 102 | 250.00 |
案例一:计算每日总销售额的日环比
我们想计算每天的总销售额以及与前一天的环比增长。
SELECT
sale_date,
SUM(amount) AS daily_total,
LAG(SUM(amount), 1) OVER (ORDER BY sale_date) AS previous_day_total,
(SUM(amount) - LAG(SUM(amount), 1) OVER (ORDER BY sale_date)) / LAG(SUM(amount), 1) OVER (ORDER BY sale_date) AS daily_growth_rate
FROM sales
GROUP BY sale_date
ORDER BY sale_date;
输出:
| sale_date | daily_total | previous_day_total | daily_growth_rate |
|------------|------------:|-------------------:|------------------:|
| 2026-10-01 | 250.00 | NULL | NULL |
| 2026-10-02 | 280.00 | 250.00 | 0.12 |
| 2026-10-03 | 280.00 | 280.00 | 0.00 |
| 2026-10-04 | 310.00 | 280.00 | 0.10714285714285714 |
| 2026-10-05 | 330.00 | 310.00 | 0.06451612903225806 |
| 2026-10-06 | 260.00 | 330.00 | -0.21393939393939394 |
| 2026-10-07 | 280.00 | 260.00 | 0.07692307692307693 |
| 2026-10-08 | 450.00 | 280.00 | 0.6071428571428571 |
案例二:找出销售额连续增长的产品
假设我们想找出在某个时间段内,销售额连续几天都在增长的产品。这里 QUALIFY 就显得尤为重要,因为它允许我们在窗口函数计算后进行过滤。
WITH daily_sales_by_product AS (
SELECT
sale_date,
product_id,
SUM(amount) AS daily_amount
FROM sales
GROUP BY sale_date, product_id
),
lagged_sales AS (
SELECT
sale_date,
product_id,
daily_amount,
LAG(daily_amount, 1) OVER (PARTITION BY product_id ORDER BY sale_date) AS prev_daily_amount
FROM daily_sales_by_product
)
SELECT
sale_date,
product_id,
daily_amount
FROM lagged_sales
QUALIFY daily_amount > prev_daily_amount
ORDER BY product_id, sale_date;
输出:
| sale_date | product_id | daily_amount |
|------------|-----------:|-------------:|
| 2026-10-02 | 101 | 120.00 |
| 2026-10-04 | 101 | 130.00 |
| 2026-10-05 | 101 | 140.00 |
| 2026-10-08 | 101 | 200.00 |
| 2026-10-02 | 102 | 160.00 |
| 2026-10-03 | 102 | 170.00 |
| 2026-10-04 | 102 | 180.00 |
| 2026-10-05 | 102 | 190.00 |
| 2026-10-08 | 102 | 250.00 |
权威参考
- DuckDB 官方文档 - 窗口函数: https://duckdb.org/docs/sql/window_functions
- DuckDB 官方文档 - QUALIFY 子句: https://duckdb.org/docs/sql/query_syntax/qualify
小结
QUALIFY 子句是 DuckDB 中一个非常强大的特性,它与窗口函数结合,能够极大简化复杂的时序分析逻辑,提升查询性能。通过本文的案例,你可以看到如何利用这一组合,避免繁琐的自连接和子查询,以更优雅的方式处理数据。
继续深入
如果你对 DuckDB 的高级功能或性能优化感兴趣,这里有几篇推荐文章:
- DuckDB 安装使用教程(全平台) — 还没装 DuckDB 的话,先跑通 CLI。
- DuckDB 完全指南 — 从数据模型到高级特性的系统读物。
- DuckDB 聚合函数进阶:STRING_AGG、ARRAY_AGG、MAP_AGG — 深入了解 DuckDB 的聚合功能。
- DuckDB Agent AI大脑:打造智能数据分析助手 — 探索 DuckDB 如何与 AI 结合。
验证环境:DuckDB 1.x/2.x(本文代码基于当前稳定版 CLI/Python 客户端实测)
完整可运行代码与测试脚本:见 duckdblab-examples 仓库
可复现验证
本文 SQL/Python 代码均可直接复制运行,测试条件与环境已在文中说明;如复现结果有差异,可对照上方版本与数据集说明。