SQL 课程 17:窗口函数:排名与累计值

GROUP BY 会把多行压缩成一行,但很多分析既想保留每笔明细,又想在旁边看到它在组内的排名或累计值。窗口函数正好解决这个问题:它会“看着一组行计算”,却不会把明细行折叠掉。

本课使用 RANK、DENSE_RANK、ROW_NUMBER 和 SUM() OVER。PARTITION BY 决定在哪个业务范围内计算,ORDER BY 决定排名或累计的先后顺序。

窗口函数里的排序是分析口径的一部分。做累计成交金额时,如果同一天有多笔商机,就需要用 opportunity_id 作为第二排序条件,保证每一步都能复现。

在保留明细的同时计算窗口指标
函数(...) OVER (
  PARTITION BY 分组字段
  ORDER BY 排序字段
  ROWS BETWEEN ... AND ...
) AS 窗口指标

窗口函数与 GROUP BY 的区别

GROUP BY 的结果是一组一个汇总值,例如每位销售人员一行;窗口函数会把这个值写回组内的每条记录。例如一笔成交商机既可以保留自己的 close_value,也可以同时显示它在产品内的金额排名。

可以把窗口想成一扇移动的观察窗:PARTITION BY 划定窗户属于哪个组,ORDER BY 决定窗户从哪一行开始移动。

计算每个产品内的成交金额排名
SELECT product, opportunity_id, close_value,
       RANK() OVER (
         PARTITION BY product
         ORDER BY close_value DESC
       ) AS rank_in_product
FROM sales_pipeline
WHERE deal_stage = 'Won';

小提醒:RANK 遇到并列值会跳号;DENSE_RANK 遇到并列值不跳号;ROW_NUMBER 即使金额相同也会给每行一个唯一序号。

排名函数:先选对含义,再选函数

RANK 适合回答“这笔商机在同产品中排第几”,并列第一后下一名会是第三。DENSE_RANK 适合连续名次。ROW_NUMBER 更适合去重或取每组前 N 条,因为每一行都有唯一编号。

三种常用排名函数

函数并列时的表现适合场景
RANK()会跳号竞赛式排名
DENSE_RANK()不跳号连续等级排名
ROW_NUMBER()每行唯一序号去重、取每组前 N 条

累计值需要明确窗口范围

SUM(close_value) OVER (PARTITION BY sales_agent ORDER BY close_date, opportunity_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 会从每位销售人员的第一笔成交开始,逐笔累加到当前记录。

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 的意思是“从本组第一行到当前行”。如果没有稳定的 ORDER BY,累计值的每一步就可能因为同一天记录的先后不确定而难以复核。

练习

窗口题会保留明细行,并严格核对窗口计算结果和最终排序。请不要把窗口函数误写成会压缩行数的 GROUP BY。

表格:sales_pipeline(商机)

查询返回 0 行。

显示第 00
1 / 1
正在准备浏览器内 SQL 数据库…