当前位置: 首页 > news >正文

从聚合到透视:SQL 窗口函数的系统解读

目录

引言

1. 窗口函数的基本概念与语法骨架

1.1 语法框架

1.2 执行顺序

2. 排名三兄弟:ROW_NUMBER、RANK、DENSE_RANK

2.1 语义差异

2.2 运行示例

2.3 结果解读

3. 偏移双雄:LAG 与 LEAD

3.1 语法与参数

3.2 环比增长率计算示例

3.3 结果验证

4. 综合案例:Top-N 与同比环比一次查

4.1 数据准备

4.2 查询实现

4.3 结果

5. 性能优化与执行计划

5.1 索引策略

5.2 执行计划剖析(以 PostgreSQL 为例)

6. 常见误区与最佳实践

7. 小结与展望


引言

        SQL 自 1974 年诞生以来,其查询能力经历了从简单投影、选择、连接到 OLAP 多维分析的多轮进化。2003 年,ANSI SQL 正式将“窗口函数(Window Function)”纳入规范,使分析型查询可以在不引入子查询或自连接的情况下,完成排序、累计、同比、环比、Top-N 等复杂计算。本文聚焦于最常用的五类窗口函数——ROW_NUMBER、RANK、DENSE_RANK、LAG 与 LEAD——从语法、语义、性能、实战四个维度进行系统梳理,并辅以可以复制运行的示例与对比表格,帮助读者在真实业务场景中迅速落地。


1. 窗口函数的基本概念与语法骨架

        窗口函数的核心思想是:在结果集的“窗口”内执行聚合或排序计算,但不减少输出行数。这与传统的 GROUP BY 形成鲜明对比——GROUP BY 会把多行聚合成一行,而窗口函数保留明细行,仅在其旁增加一列计算结果。

1.1 语法框架

window_function_name ( [expression [, ...]] )
OVER ([PARTITION BY partition_expression [, ...]][ORDER BY sort_expression [ASC|DESC] [NULLS {FIRST|LAST}] [, ...]][frame_clause]
)
  • PARTITION BY 类似于 GROUP BY,定义窗口边界。

  • ORDER BY 定义窗口内行的逻辑顺序,直接影响排名函数与偏移函数的行为。

  • frame_clause 指定“滑动窗口”范围,如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

1.2 执行顺序

一条 SELECT 语句的逻辑执行顺序为:
FROM → WHERE → GROUP BY → HAVING → SELECT (含窗口函数) → DISTINCT → ORDER BY → LIMIT
        窗口函数在 SELECT 阶段执行,因此它可以引用 SELECT 列表中定义的列别名,却无法在 WHERE、GROUP BY 中直接使用


2. 排名三兄弟:ROW_NUMBER、RANK、DENSE_RANK

        在 OLAP 报表中,“取每个分组的前 N 名”是最常见的诉求。三种排名函数的区别在于对并列值的处理。

2.1 语义差异

函数名并列值是否占用名次名次是否连续典型场景示例
ROW_NUMBER()去重、唯一序号
RANK()学生成绩排名,允许并列第 2
DENSE_RANK()等级划分,如 A/B/C 档

2.2 运行示例

        假设存在销售表 sales(order_id, sales_date, shop_id, amount),计算每个门店按月销售额排名:

SELECT shop_id,DATE_TRUNC('month', sales_date) AS mon,SUM(amount)                     AS mon_amt,ROW_NUMBER() OVER (PARTITION BY shop_id ORDER BY SUM(amount) DESC) AS rn,RANK()       OVER (PARTITION BY shop_id ORDER BY SUM(amount) DESC) AS rnk,DENSE_RANK() OVER (PARTITION BY shop_id ORDER BY SUM(amount) DESC) AS drnk
FROM   sales
GROUP  BY shop_id, DATE_TRUNC('month', sales_date);

2.3 结果解读

  • rn 列始终唯一,即使金额相同也会给出 1、2、3…

  • rnk 列出现并列时跳过后续名次,例如两个 1 名后紧跟 3 名。

  • drnk 列在并列后名次连续,例如两个 1 名后仍是 2 名。


3. 偏移双雄:LAG 与 LEAD

        排名函数回答“我是谁”,而偏移函数回答“我的邻居是谁”。LAG/LEAD 通过向前/向后抓取第 N 行的值,实现同比、环比、差分计算。

3.1 语法与参数

LAG(column, offset, default) OVER (PARTITION BY ... ORDER BY ...)
LEAD(column, offset, default) OVER (PARTITION BY ... ORDER BY ...)
  • column:要取值的列。

  • offset:偏移量,正整数,默认为 1。

  • default:当偏移后越界时的回退值,省略则为 NULL。

3.2 环比增长率计算示例

继续用 sales 表,计算每个门店的月度环比增长率:

WITH month_sum AS (SELECT shop_id,DATE_TRUNC('month', sales_date) AS mon,SUM(amount)                     AS amtFROM   salesGROUP  BY shop_id, DATE_TRUNC('month', sales_date)
),
lag_tab AS (SELECT *,LAG(amt, 1) OVER (PARTITION BY shop_id ORDER BY mon) AS prev_amtFROM   month_sum
)
SELECT shop_id,mon,amt,prev_amt,CASEWHEN prev_amt IS NULL THEN NULLELSE ROUND( (amt - prev_amt) * 100.0 / prev_amt, 2)END AS mom_rate
FROM   lag_tab
ORDER  BY shop_id, mon;

3.3 结果验证

  • 第一条记录 prev_amt 为 NULL,环比率为空,符合业务直觉。

  • 通过 LEAD 亦可计算“未来第 N 天”的预测值,只需把 LAG 改为 LEAD 即可。


4. 综合案例:Top-N 与同比环比一次查

        真实报表常要求“每个品类下销售额 Top3 店铺,并展示其本月、上月、去年同期销售额”。传统写法需多层子查询,窗口函数可将逻辑浓缩至一层。

4.1 数据准备

为聚焦核心逻辑,本节使用 CTE 构造简化表:

CREATE TEMP TABLE sales_demo AS
SELECT *
FROM (VALUES(202407, 'A', 'S1', 100),(202407, 'A', 'S2', 90),(202407, 'A', 'S3', 80),(202407, 'A', 'S4', 70),(202406, 'A', 'S1', 95),(202406, 'A', 'S2', 85),(202406, 'A', 'S3', 75),(202406, 'A', 'S4', 65),(202307, 'A', 'S1', 80),(202307, 'A', 'S2', 70),(202307, 'A', 'S3', 60),(202307, 'A', 'S4', 50)
) AS t(ym, category, shop_id, amt);

4.2 查询实现

WITH ranked AS (SELECT *,ROW_NUMBER() OVER (PARTITION BY ym, category ORDER BY amt DESC) AS rnFROM   sales_demo
),
current_top AS (SELECT * FROM ranked WHERE ym = 202407 AND rn <= 3
),
joined AS (SELECT c.ym        AS cur_ym,c.category,c.shop_id,c.amt       AS cur_amt,LAG(c.amt, 1)  OVER (PARTITION BY c.shop_id ORDER BY c.ym)      AS last_m,LAG(c.amt, 12) OVER (PARTITION BY c.shop_id ORDER BY c.ym)      AS last_yFROM   ranked cWHERE  c.rn <= 3
)
SELECT * FROM joined WHERE cur_ym = 202407;

SQL 的总体思路
“先排名 → 再筛选 → 再偏移” 三步走:

(1) ranked:给 每个 ym、category 组合内部 按 amt 降序打上序号 rn。
(2) current_top:把 202407 且 rn<=3 的行抽出来,得到“本月 Top3”。
(3) joined:
- 先把 ranked 里 所有月份、但只保留 Top3 店铺 的行留下来(即每个 ym 的 Top3)。
- 然后按 shop_id 分区,按 ym 排序,用 LAG 把上一行(上月)和上 12 行(去年同月)的 amt 抓过来。
(4) 最后再用 WHERE cur_ym = 202407 把非 202407 的行过滤掉,只留下 3 行结果。

“先给所有月份排座次,留下每个座次里的尖子生(Top3),再用时间轴把他们的历史成绩抄过来,最后只取最新一期的报告。”

4.3 结果

cur_ymcategoryshop_idcur_amtlast_mlast_y
202407AS11009580
202407AS2908570
202407AS3807560

5. 性能优化与执行计划

        窗口函数虽优雅,但在大数据量下仍需关注性能。主流引擎(PostgreSQL、MySQL 8+、SQL Server、Oracle、BigQuery、Snowflake)均已实现基于排序或哈希的窗口算子。

5.1 索引策略

  • ORDER BY 列与 PARTITION BY 列共同构成排序键,建立复合索引可减少排序开销。

  • 若仅使用 ROW_NUMBER 而无 ORDER BY,则优化器退化为任意排序,结果不稳定,应避免。

5.2 执行计划剖析(以 PostgreSQL 为例)

EXPLAIN (ANALYZE, BUFFERS)
SELECT shop_id,RANK() OVER (PARTITION BY shop_id ORDER BY amount DESC)
FROM   sales;

典型输出:

WindowAgg  (cost=... rows=...)->  Sort  (cost=... rows=...)Sort Key: shop_id, amount DESC->  Seq Scan on sales ...

当数据量达到千万级,可考虑使用物化视图或增量刷新策略,将窗口结果缓存至每日离线任务,线上直接查询。


6. 常见误区与最佳实践

误区描述正确做法
在 WHERE 子句中直接引用窗口函数别名使用子查询或 CTE 先计算窗口列再过滤
忽略 NULLS FIRST/LAST 导致排序不稳定显式指定 NULL 顺序,确保结果可复现
认为 DENSE_RANK 一定优于 RANK依据业务需求选择,等级划分用 DENSE_RANK,真实排名用 RANK
在 MySQL 5.x 使用窗口函数升级至 8.0+ 或使用变量模拟(性能差)

7. 小结与展望

        窗口函数将 SQL 从“集合查询语言”推进到“数据分析语言”。本文通过五类高频函数的语法、对比、实战、性能、误区五个角度进行剖析,辅以可直接运行的示例,旨在让读者不仅“会用”,更能“用好”。未来,随着 ANSI SQL 引入 WINDOW 子句命名窗口以及 GROUPS 模式,窗口函数将在流式计算(Flink、Kafka Streams)与机器学习特征工程领域发挥更大作用。希望读者在业务实践中继续深挖,将窗口函数与索引、物化视图、增量计算相结合,打造高性能、低延迟的实时分析系统。

http://www.dtcms.com/a/328666.html

相关文章:

  • 谷歌、facebook、tiktok广告账户多开怎么安全?亚马逊、ebay、shopee多店铺怎么做好?看看adspower工具,注册免费试用及实用技巧分享
  • SQL详细语法教程(一)--数据定义语言(DDL)
  • 基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算)实践
  • 4G模块 ML307A通过MQTT协议连接到阿里云
  • 数据科学与爬虫技术学习笔记
  • 基于机器学习的自动驾驶汽车新型失效运行方法
  • Win11和Mac设置环境变量
  • 【汽车标定数据】动态优先级线程池在异步多文件解析中的应用
  • 2022 年全国硕士研究生招生考试真题笔记
  • 深度学习赋能汽车制造缺陷检测
  • “我店模式”:零售转型中的场景化突围
  • 美团搜索推荐统一Agent之交互协议与多Agent协同
  • 【计算机网络 | 第6篇】计算机体系结构与参考模型
  • go学习笔记-匿名函数
  • 算法题笔记
  • Java连接MySQL数据库
  • Socket 套接字常用方法
  • Java多源AI接口融合框架:动态模型切换与智能路由实战
  • pybind11绑定C++项目心得
  • Sentinel 和 Hystrix
  • MySQL 存储过程终止执行的方法
  • 力扣热题100------279.完全平方数
  • XGBoost 的适用场景以及与 CNN、LSTM 的区别
  • AQS的原理与ReentrantLock的关系
  • 基于Rocky Linux 9的容器化部署方案,使用Alpine镜像实现轻量化
  • 企业高性能web服务器(3)
  • Linux学习-应用软件编程(文件IO)
  • 【科研绘图系列】R语言绘制特定区域颜色标记散点图
  • Pytest项目_day13(usefixture方法、params、ids)
  • 【不动依赖】Kali Linux 2025.2 中安装mongosh