DuckDB 时序分析利器:QUALIFY 与窗口函数实战

还在为复杂的时序数据分析头疼?DuckDB 的 QUALIFY 结合强大的窗口函数,能让你在不写子查询、不建临时表的情况下,轻松完成环比、同比、滑动平均等高级分析,大幅提升查询性能与代码简洁度。本文通过实战案例,深入解析这一组合在实时数据分析中的强大应用。

痛点:传统时序分析的效率瓶颈

在数据分析领域,时序数据(Time Series Data)无处不在,从销售额、用户活跃度到服务器监控指标。但对时序数据进行复杂的分析,比如计算日环比、周同比、连续增长天数或滑动平均值,常常伴随着以下挑战:

  1. 复杂的 SQL 语句: 传统上,你可能需要编写多个子查询(Subquery)或使用自连接(Self-Join)来获取历史数据,导致 SQL 语句冗长、难以理解。
  2. 性能瓶颈: 大量子查询和自连接会导致数据多次扫描和中间结果集的生成,尤其在处理海量时序数据时,查询性能会急剧下降。
  3. 临时表依赖: 为了简化逻辑或提升性能,分析师有时会创建临时表来存储中间计算结果,增加了数据处理流程的复杂性。

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 |

权威参考

小结

QUALIFY 子句是 DuckDB 中一个非常强大的特性,它与窗口函数结合,能够极大简化复杂的时序分析逻辑,提升查询性能。通过本文的案例,你可以看到如何利用这一组合,避免繁琐的自连接和子查询,以更优雅的方式处理数据。


继续深入

如果你对 DuckDB 的高级功能或性能优化感兴趣,这里有几篇推荐文章:


验证环境:DuckDB 1.x/2.x(本文代码基于当前稳定版 CLI/Python 客户端实测)

完整可运行代码与测试脚本:见 duckdblab-examples 仓库


可复现验证

本文 SQL/Python 代码均可直接复制运行,测试条件与环境已在文中说明;如复现结果有差异,可对照上方版本与数据集说明。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials