惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
博客园 - 【当耐特】
WordPress大学
WordPress大学
爱范儿
爱范儿
美团技术团队
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
小众软件
小众软件
量子位
Hugging Face - Blog
Hugging Face - Blog
B
Blog RSS Feed
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
H
Hackread – Cybersecurity News, Data Breaches, AI and More
腾讯CDC
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
博客园 - 叶小钗
GbyAI
GbyAI
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Full Disclosure
G
Google Developers Blog
D
Docker
T
Tailwind CSS Blog
C
Check Point Blog
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
T
The Blog of Author Tim Ferriss
B
Blog
博客园 - 三生石上(FineUI控件)
博客园 - Franky
H
Help Net Security
MyScale Blog
MyScale Blog
U
Unit 42
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog
L
LangChain Blog
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog

博客园 - 小纸条

模型微调 ruoyiai 启动指南 反向传播 numpy的使用 B 和 B+树 红黑树 ruoyi-vue 梯度下降法 博弈论 离散化 AcWing 907. 区间覆盖 AcWing 906. 区间分组 AcWing 908 最大不相交区间数量 AcWing 905. 区间选点 AcWing 104. 货仓选址 动态规划经典题 1226. 哲学家进餐 1195. 交替打印字符串 1117. H2O 生成 1116. 打印零与奇偶数 关联子查询
窗口函数
小纸条 · 2025-11-02 · via 博客园 - 小纸条

🪄 一、窗口函数简介

窗口函数(Window Function)用于对查询结果集中的每一行计算“相对排名”或“顺序编号”。

三大常用函数:

函数功能描述相同值处理是否跳号示例序号
ROW_NUMBER() 为每行生成唯一连续编号 不并列 不跳号 1, 2, 3, 4, ...
RANK() 并列时相同排名 并列 跳号 1, 2, 2, 4, ...
DENSE_RANK() 并列时相同排名 并列 不跳号 1, 2, 2, 3, ...

🧱 二、示例表

CREATE TABLE scores (
    student_name VARCHAR(10),
    score INT
);

INSERT INTO scores VALUES
('张三', 95),
('李四', 92),
('王五', 92),
('赵六', 88),
('孙七', 85);

💻 三、使用窗口函数实现排名

SELECT
    student_name,
    score,
    ROW_NUMBER() OVER (ORDER BY score DESC) AS rn,
    RANK()       OVER (ORDER BY score DESC) AS rk,
    DENSE_RANK() OVER (ORDER BY score DESC) AS dr
FROM scores;

📊 查询结果

student_namescoreROW_NUMBERRANKDENSE_RANK
张三 95 1 1 1
李四 92 2 2 2
王五 92 3 2 2
赵六 88 4 4 3
孙七 85 5 5 4

🧠 四、区别详解

函数并列情况下一名解释
ROW_NUMBER() 不并列 连续 每行唯一编号,即使分数相同
RANK() 并列 跳号 类似比赛排名(两个第二名后直接第四名)
DENSE_RANK() 并列 不跳号 等级划分更紧密(1,2,2,3,4)

⚙️ 五、在不支持窗口函数的数据库中用自联结实现

有些旧版本数据库(如 MySQL 5.x)不支持窗口函数,我们可以通过自联结 (Self Join) 实现相同逻辑。


🧩 1️⃣ 模拟 RANK()

思路:
计算比当前行分数更高的行数(包括当前行自己)
→ 直接用 >= 替代 > + 1

SELECT 
    a.student_name,
    a.score,
    COUNT(b.student_name) AS rank
FROM scores a
LEFT JOIN scores b
  ON b.score >= a.score
GROUP BY a.student_name, a.score
ORDER BY a.score DESC;

📈 结果:

student_namescorerank
张三 95 1
李四 92 2
王五 92 2
赵六 88 4
孙七 85 5

✅ 等价于 RANK() OVER (ORDER BY score DESC)


🧩 2️⃣ 模拟 DENSE_RANK()

思路:
统计“比当前行分数高的不同分数”数量,再加上自己。

SELECT 
    a.student_name,
    a.score,
    COUNT(DISTINCT b.score) AS dense_rank
FROM scores a
LEFT JOIN scores b
  ON b.score >= a.score
GROUP BY a.student_name, a.score
ORDER BY a.score DESC;

📈 结果:

student_namescoredense_rank
张三 95 1
李四 92 2
王五 92 2
赵六 88 3
孙七 85 4

✅ 等价于 DENSE_RANK() OVER (ORDER BY score DESC)


🧩 3️⃣ 模拟 ROW_NUMBER()

思路:
ROW_NUMBER() 必须每行唯一编号,不能并列。
所以在分数相同的情况下,还需要用另一个唯一字段打破并列(如 student_nameid)。

SELECT 
    a.student_name,
    a.score,
    COUNT(b.student_name) AS row_number
FROM scores a
LEFT JOIN scores b
  ON b.score > a.score
   OR (b.score = a.score AND b.student_name < a.student_name)
GROUP BY a.student_name, a.score
ORDER BY a.score DESC, a.student_name;

📈 结果:

student_namescorerow_number
张三 95 1
李四 92 2
王五 92 3
赵六 88 4
孙七 85 5

✅ 等价于 ROW_NUMBER() OVER (ORDER BY score DESC, student_name ASC)


PARTITION BY 详解

PARTITION BY用于在窗口函数中进行分组计算,类似于 GROUP BY的概念,但关键区别是:它不会合并行,而是在每个分组内独立进行计算

基本语法

函数名() OVER (
    PARTITION BY 分组字段 
    ORDER BY 排序字段
)

具体示例

假设我们有一个更详细的学生成绩表:

student_idstudent_nameclass_namescore
1 张三 一班 95
2 李四 一班 92
3 王五 一班 88
4 赵六 二班 96
5 孙七 二班 90
6 周八 二班 90
7 吴九 二班 85

查询语句:计算每个班级内部的排名

SELECT
    student_id,
    student_name,
    class_name,
    score,
    ROW_NUMBER() OVER (PARTITION BY class_name ORDER BY score DESC) AS rn,
    RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS rk,
    DENSE_RANK() OVER (PARTITION BY class_name ORDER BY score DESC) AS dr
FROM students;

查询结果:

student_idstudent_nameclass_namescorernrkdr
1 张三 一班 95 1 1 1
2 李四 一班 92 2 2 2
3 王五 一班 88 3 3 3
4 赵六 二班 96 1 1 1
5 孙七 二班 90 2 2 2
6 周八 二班 90 3 2 2
7 吴九 二班 85 4 4 3

结果分析

一班的情况:

  • 张三:95分,班级第1名(rn=1, rk=1, dr=1)
  • 李四:92分,班级第2名(rn=2, rk=2, dr=2)
  • 王五:88分,班级第3名(rn=3, rk=3, dr=3)

二班的情况(注意排名重置):

  • 赵六:96分,在二班内重新从第1名开始排名(rn=1, rk=1, dr=1)
  • 孙七周八:都是90分,出现并列
    • ROW_NUMBER():强制分配2和3(不确定顺序)
    • RANK():都排名第2,下一个吴九排名第4(跳过第3名)
    • DENSE_RANK():都排名第2,下一个吴九排名第3(连续)

PARTITION BY 的更多实用例子

1. 查询每个班级的前2名学生

SELECT * FROM (
    SELECT
        student_name,
        class_name,
        score,
        ROW_NUMBER() OVER (PARTITION BY class_name ORDER BY score DESC) as rank_in_class
    FROM students
) AS ranked_students
WHERE rank_in_class <= 2;

2. 计算每个学生的成绩在班级内的百分比排名

SELECT
    student_name,
    class_name,
    score,
    ROUND(
        PERCENT_RANK() OVER (PARTITION BY class_name ORDER BY score) * 100, 2
    ) as percentile_in_class
FROM students;

3. 与其他窗口函数结合使用

SELECT
    student_name,
    class_name,
    score,
    -- 班级内排名
    RANK() OVER (PARTITION BY class_name ORDER BY score DESC) as class_rank,
    -- 班级内平均分
    AVG(score) OVER (PARTITION BY class_name) as class_avg,
    -- 班级内最高分
    MAX(score) OVER (PARTITION BY class_name) as class_max
FROM students;

PARTITION BY 的核心优势

  1. 分组不合并:在每个分组内独立计算,但保持所有原始数据行
  2. 排名重置:每个分组的排名都从1重新开始
  3. 灵活组合:可以按多个字段分区:PARTITION BY class_name, subject_name
  4. 性能优化:比使用多个子查询或自连接更高效

PARTITION BY它实现了"既要分组计算,又要保留明细数据"的需求。