窗口函数与 CTE

kp-005 · 数据模型与SQL基础核心约 20 分钟已校对

前置知识点

前置:

相关知识点

相关:

学习进度:

一句话定义

窗口函数(Window Function)在不折叠行的前提下,按「窗口」(OVER 子句定义的分组与排序范围)为每一行计算聚合或排名;CTE(Common Table Expression,WITH 子句)给一段查询命名以复用与分层,两者共同把复杂 SQL 从「嵌套面条」变成「清晰流水线」。

为什么重要

前置知识

kp-004(聚合与子查询)。

核心概念

原理与机制

窗口函数在逻辑执行顺序中位于 GROUP BY 之后、ORDER BY 之前:它拿到的是「已过滤、未分组折叠」的行集,按 PARTITION 划分排序后逐行计算。排名三函数的差异来自并列处理:ROW_NUMBER 强行编号;RANK 并列同名次、下一名跳号;DENSE_RANK 跳号。帧默认为 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,当排序列有并列值时 RANGE 会把并列行一并纳入——这就是「SUM 窗口结果与预期不符」的常见根因,显式写 ROWS 可消除歧义。递归 CTE 的机制是迭代物化:定位成员产出初始行集,递归成员反复引用 CTE 自身直至空集,深度过大需 LIMIT 防失控。

实例或案例

「每类商品中销量最高的前 2 条」(经典 Top-N per group):

WITH ranked AS (
  SELECT product_id, category, sales,
         ROW_NUMBER() OVER (PARTITION BY category
                            ORDER BY sales DESC) AS rn
  FROM products
)
SELECT product_id, category, sales
FROM ranked
WHERE rn <= 2
ORDER BY category, rn;

组织架构树展开(递归 CTE):

WITH RECURSIVE sub (emp_id, mgr_id, depth) AS (
  SELECT emp_id, mgr_id, 0 FROM employees WHERE mgr_id IS NULL
  UNION ALL
  SELECT e.emp_id, e.mgr_id, s.depth + 1
  FROM employees e JOIN sub s ON e.mgr_id = s.emp_id
)
SELECT * FROM sub ORDER BY depth;

常见误区

自测题

  1. RANK 与 DENSE_RANK 在序列 1、2、2、3 上的输出? 答:RANK 输出 1、2、2、4(跳号);DENSE_RANK 输出 1、2、2、3(不跳号)。
  2. 为什么窗口 SUM 默认帧用 RANGE 时结果可能偏大? 答:RANGE 按「值」把与当前行排序键并列的行都纳入帧,并列值被重复计入;需按行计数时应显式 ROWS BETWEEN ...。

公式或模型

移动平均(窗口宽 k):MA_t = (1/k) × Σ_{i=t-k+1}^{t} x_i,用 AVG(x) OVER (ORDER BY t ROWS BETWEEN k-1 PRECEDING AND CURRENT ROW) 表达。

图示

本节不适用:窗口计算过程用上文「销售序列 + 三种排名函数」的问答已可自验,静态图示增益有限。

直观类比

GROUP BY 像把全班按小组收作业、每组只交一份汇总表;窗口函数则像给每个学生「在保留原座位的同时」发一张写有「本组排名、本组平均分」的便签——人还是那些人,信息更厚了。

与其他知识点的关系

kp-004 的 GROUP BY 是窗口的对照概念;递归 CTE 与 kp-027 的图数据库查询能力互为替代;复杂窗口查询的性能诊断入口是 kp-012。

延伸阅读