我写了一篇关于理解窗口函数的文章。
吐槽一下 :写这篇文章不是为了背具体的 API 名字。在国内的面试环境中,人们往往期待你能够复述出准确的函数或方法。但对我这样一个更偏向理解和推理的人来说,其实我完全可以在不死记硬背的情况下写出需要的代码 。真正重要的是掌握底层逻辑,并能解决实际问题,而不是仅仅记住一个定义。
我从来不会鼓励所谓的盲目走别人走过的路,大部分人坚持的也不一定是对的。
这篇文章更多是关于如何重塑我对技术概念的表达方式。我想要致敬那个曾经的自己------那个在死记硬背的泥沼里挣扎的自己------并展示出:其实有另一种方式,可以按照自己的理解去解释和学习技术。
每当我重新诠释并分享所谓的"标准答案"时,我不仅仅是在分享知识,我也是在为过去那个因为记忆力不足而被误解的自己正名。
我希望别人能明白,学习技术并不是为了背名词------那些随时可以用搜索引擎查到。真正的核心是:你能不能理解、应用并解决问题。这才是技术的本质。
总结:
✅ 小技巧:别去死记函数,按类别去思考:
- 想要顺序/排名 → 用 位置类函数
- 想要累计和/滑动平均 → 用 聚合类函数
- 想要邻居信息 → 用 访问其他行的函数
- 想要分布/百分比 → 用 比例类函数
窗口函数简化理解
关键点: 你不需要记住所有函数名,但你必须知道 什么时候用窗口函数,为什么用。
窗口函数描述的是:一行数据要么和它的邻居有关,要么和整个数据集有关。
我喜欢把它们分成 4 个方向:看邻居、看临近区域、看序列顺序、看整体分布。
吐槽一下:我想先把结论抛出来
✅ 小技巧:不要死记函数,按类别去思考:
- "我想排名 → 用 位置类函数"
- "我想累计求和 → 用 累计/移动聚合函数"
- "我想看邻居 → 用 LAG/LEAD"
- "我想看分布/百分比 → 用 累计/比例函数"
四大类别
1️⃣ 位置指标(Position / Ranking)
关注点:序列里的顺序 / 排名 / 分桶
- 例子:
row_number
,rank
,dense_rank
,ntile
2️⃣ 累计 / 移动聚合(Aggregation / Moving / Cumulative)
关注点:从开始到当前行的累计,或者在一个滑动窗口里的聚合
- 例子:累计和 (cumulative sum),移动平均 (moving average)
3️⃣ 访问其他行(Accessing Other Rows)
关注点:不做聚合,只是看看邻居的数据
- 例子:上一行 (
LAG
),下一行 (LEAD
)
4️⃣ 比例 / 分布(Proportion / Distribution)
关注点:一个值在整体数据中的位置 / 分布
- 例子:累计分布 (cumulative distribution),百分位排名 (percent rank)
窗口函数在 SQL 中的执行顺序
可以这样理解 SQL 的逻辑执行顺序:
1️⃣ FROM / JOIN
→ 拿到数据表
2️⃣ WHERE
→ 过滤行
3️⃣ GROUP BY
→ 分组
4️⃣ HAVING
→ 对分组结果再过滤
5️⃣ 窗口函数 (OVER) → 在组或整张表上计算
(⚠️ 注意:在 GROUP BY
之后,但在 SELECT
阶段完成,不是在 ORDER BY
之后)
6️⃣ SELECT
→ 选择列
7️⃣ ORDER BY
→ 排序
8️⃣ LIMIT
→ 限制返回的行数
示例表
sql
CREATE TABLE Queue (
turn INT,
person_name VARCHAR(50),
weight INT
);
INSERT INTO Queue (turn, person_name, weight) VALUES
(1, 'Alice', 250),
(2, 'Alex', 350),
(3, 'John Cena', 400),
(4, 'Marie', 200),
(5, 'Bob', 175),
(6, 'Winston', 500);
1️⃣ 累计 / 移动聚合
sql
SELECT
turn,
person_name,
weight,
SUM(weight) OVER (ORDER BY turn ROWS UNBOUNDED PRECEDING) AS total_weight,
AVG(weight) OVER (ORDER BY turn ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM Queue;
total_weight
:从第一行到当前行的累计和moving_avg
:当前行 + 前两行的平均值
2️⃣ 排名 / 顺序
sql
SELECT
person_name,
weight,
ROW_NUMBER() OVER (ORDER BY weight DESC) AS row_num,
RANK() OVER (ORDER BY weight DESC) AS rank_by_weight,
DENSE_RANK() OVER (ORDER BY weight DESC) AS dense_rank,
NTILE(3) OVER (ORDER BY weight) AS ntile_3
FROM Queue;
row_num
:顺序编号rank_by_weight
:按体重排名,有并列会跳过名次dense_rank
:按体重排名,有并列但不跳过名次ntile_3
:分成 3 个桶
3️⃣ 访问其他行
sql
SELECT
person_name,
weight,
LAG(weight, 1) OVER (ORDER BY turn) AS prev_weight,
LEAD(weight, 1) OVER (ORDER BY turn) AS next_weight
FROM Queue;
prev_weight
:上一行的体重next_weight
:下一行的体重
4️⃣ 累计 / 百分比 / 分布
sql
SELECT
person_name,
weight,
CUME_DIST() OVER (ORDER BY weight) AS weight_cum_dist,
PERCENT_RANK() OVER (ORDER BY weight) AS weight_percent_rank,
NTILE(3) OVER (ORDER BY weight) AS ntile_3
FROM Queue;
CUME_DIST()
:当前行在队列中所占比例(累计分布)PERCENT_RANK()
:百分位排名NTILE(3)
:分成 3 个桶