约 767 字
10 分钟

数据库中的窗口函数

2026年7月22日
2026年7月27日
随手笔记
摘要

PostgreSQL 的窗口函数(Window Functions)是数据分析的神器,它允许你在不减少行数的前提下,对与当前行相关的一组行(即“窗口”)执行聚合或排名计算。

先创建一个订单

sql
CREATE TABLE orders
(
    order_id    SERIAL PRIMARY KEY,
    customer_id INT            NOT NULL,
    order_date  DATE           NOT NULL,
    amount      NUMERIC(10, 2) NOT NULL,
    category    VARCHAR(20)    NOT NULL
);

-- 插入模拟数据(3个客户,跨3个月,含同金额并列场景)
INSERT INTO orders (customer_id, order_date, amount, category)
VALUES (1001, '2025-01-05', 200.00, '电子'),
       (1001, '2025-01-18', 350.00, '电子'),
       (1001, '2025-02-10', 350.00, '家居'),
       (1001, '2025-03-01', 500.00, '电子'),
       (1002, '2025-01-10', 150.00, '家居'),
       (1002, '2025-02-14', 600.00, '电子'),
       (1002, '2025-02-28', 400.00, '家居'),
       (1003, '2025-01-20', 800.00, '电子'),
       (1003, '2025-02-05', 300.00, '家居'),
       (1003, '2025-03-15', 900.00, '电子');

窗口函数的语法结构

sql
function_name() OVER (
    [PARTITION BY column]   -- 分组:将数据划分为多个分区(类似 GROUP BY,但不折叠行)
    [ORDER BY column]       -- 排序:定义窗口内的计算顺序
    [frame_clause]          -- 帧范围:精确定义"窗口"包含哪些行
)

基本函数

函数 说明 并列处理
ROW_NUMBER() 连续唯一序号 无并列,强制递增
RANK() 跳跃排名 并列同名次,后续跳号 (1,1,3)
DENSE_RANK() 密集排名 并列同名次,后续不跳号 (1,1,2)
NTILE(n) 等分桶编号 将分区分为 n 桶,返回桶号

所有标准聚合函数都可作窗口函数使用:SUM, AVG, COUNT, MIN, MAX 等。

举例 1.每个客户按订单金额排名(处理并列)

sql
SELECT customer_id,
       order_date,
       amount,
       RANK() OVER w       AS rank_jump,
       DENSE_RANK() OVER w AS rank_dense, -- 推荐写
       ROW_NUMBER() OVER w AS row_num -- 强制唯一: 1,2,3
FROM orders
WINDOW w AS (PARTITION BY customer_id ORDER BY amount DESC);

结果

customer_id order_date amount rank_jump rank_dense row_num
1001 2025-03-01 500.00 1 1 1
1001 2025-01-18 350.00 2 2 2
1001 2025-02-10 350.00 2 2 3
1001 2025-01-05 200.00 4 3 4
1002 2025-02-14 600.00 1 1 1
1002 2025-02-28 400.00 2 2 2
1002 2025-01-10 150.00 3 3 3
1003 2025-03-15 900.00 1 1 1
1003 2025-01-20 800.00 2 2 2
1003 2025-02-05 300.00 3 3 3

可以看到三个排序的区别,其中 rank_dense不会跳过

偏移/取值函数

函数 说明
LAG(col, n, default) 取前第 n 行的值
LEAD(col, n, default) 取后第 n 行的值
FIRST_VALUE(col) 窗口帧内第一行的值
LAST_VALUE(col) 窗口帧内最后一行的值
NTH_VALUE(col, n) 窗口帧内第 n 行的值

下面举例写法仅用于解释,不考虑运行性能

2.计算每个客户的累计消费 & 月环比增长

sql
SELECT customer_id,
       order_date,
       amount,
-- 截至当前订单的累计消费 可以看出是每次递增
       SUM(amount) OVER w    AS running_total,
-- 上一笔订单金额 LAG()取前1个的值
       LAG(amount, 1) OVER w AS prev_amount,
-- 环比增长率(%) = (本期金额 - 上期金额) / 上期金额 * 100
       ROUND(
               (amount - LAG(amount, 1) OVER w) / NULLIF(LAG(amount, 1) OVER w, 0) * 100
           , 2)              AS mom_growth_pct
FROM orders
WINDOW w AS (PARTITION BY customer_id ORDER BY order_date)
ORDER BY customer_id, order_date;

可以详细分析

customer_id order_date amount running_total prev_amount mom_growth_pct
1001 2025-01-05 200.00 200 null null
1001 2025-01-18 350.00 550 200 75
1001 2025-02-10 350.00 900 350 0
1001 2025-03-01 500.00 1400 350 42.86
1002 2025-01-10 150.00 150 null null
1002 2025-02-14 600.00 750 150 300
1002 2025-02-28 400.00 1150 600 -33.33
1003 2025-01-20 800.00 800 null null
1003 2025-02-05 300.00 1100 800 -62.5
1003 2025-03-15 900.00 2000 300 200

3.找出每个客户金额最高的 Top 2

sql
-- 使用 ROW_NUMBER() 严格保证只有两条数据, RANK()不是严格递增
SELECT *,
       ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn
FROM orders;

-- 最后结果
WITH ranked AS (SELECT *,
                       ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn
                FROM orders)
SELECT customer_id, order_date, amount, category
FROM ranked
WHERE rn <= 2
ORDER BY customer_id, amount DESC;
customer_id order_date amount category
1001 2025-03-01 500.00 电子
1001 2025-01-18 350.00 电子
1002 2025-02-14 600.00 电子
1002 2025-02-28 400.00 家居
1003 2025-03-15 900.00 电子
1003 2025-01-20 800.00 电子

4.每笔订单占该客户总消费的百分比

sql
SELECT customer_id,
       order_date,
       amount,
       SUM(amount) OVER (PARTITION BY customer_id ORDER BY amount DESC)     AS customer_total1,
       SUM(amount) OVER (PARTITION BY customer_id)                          AS customer_total2, -- 注意:无 ORDER BY = 整个分区
       ROUND(amount / SUM(amount) OVER (PARTITION BY customer_id) * 100, 2) AS pct_of_customer
FROM orders
ORDER BY customer_id, amount DESC;

可以看到 customer_total1 有 ORDER BY 后逐行递增,无 ORDER BY 整个行都是相同

可以看到每个订单明显占比

customer_id order_date amount customer_total1 customer_total2 pct_of_customer
1001 2025-03-01 500.00 500 1400 35.71
1001 2025-01-18 350.00 1200 1400 25
1001 2025-02-10 350.00 1200 1400 25
1001 2025-01-05 200.00 1400 1400 14.29
1002 2025-02-14 600.00 600 1150 52.17
1002 2025-02-28 400.00 1000 1150 34.78
1002 2025-01-10 150.00 1150 1150 13.04
1003 2025-03-15 900.00 900 2000 45
1003 2025-01-20 800.00 1700 2000 40
1003 2025-02-05 300.00 2000 2000 15
数据库