ROW_NUMBER() OVER() 窗口函数详解

复制代码
SELECT id, uid, after_value
FROM (
    SELECT id, uid, after_value, create_time,
           ROW_NUMBER() OVER (PARTITION BY uid, activity_account_code ORDER BY create_time DESC) AS rn
    FROM activity_account_flow_0
    WHERE activity_account_code = 'F4A11Z71E7DHUFh99999'
        AND uid IN (6160086500000357300, 6160086500000357100)
        AND create_time <= '2026-05-30 00:00:00'
) ranked
WHERE rn = 1; 

一、语法拆解

ROW_NUMBER() OVER (

PARTITION BY uid, activity_account_code

ORDER BY create_time DESC

) AS rn

```

这个表达式分为三个部分:

| 部分 | 关键字 | 作用 | 类比 |

|------|--------|------|------|

| **1** | `ROW_NUMBER()` | 生成行号 | 类似 Excel 的行号 |

| **2** | `PARTITION BY` | 分组依据 | 类似 `GROUP BY` |

| **3** | `ORDER BY` | 组内排序 | 决定行号顺序 |


二、通俗解释

类比:班级成绩排名

```

场景:给每个班级的学生按成绩排名

PARTITION BY class_id → 按班级分组

ORDER BY score DESC → 每个班内按成绩降序

ROW_NUMBER() → 给每个学生分配排名 (1,2,3...)

结果:

班级 A: 张三 (95 分) → 第 1 名

李四 (90 分) → 第 2 名

王五 (85 分) → 第 3 名

班级 B: 赵六 (98 分) → 第 1 名

钱七 (92 分) → 第 2 名

```


三、你的 SQL 执行过程

原始数据示例

假设有以下数据:

```

+----+---------------------+---------------------+---------------+-------------+

| id | uid | activity_account_code | after_value | create_time |

+----+---------------------+---------------------+---------------+-------------+

| 1 | 6160086500000357300 | F4A11Z71E7DHUFh99999 | 1000 | 2026-01-01 |

| 2 | 6160086500000357300 | F4A11Z71E7DHUFh99999 | 1500 | 2026-03-01 |

| 3 | 6160086500000357300 | F4A11Z71E7DHUFh99999 | 2000 | 2026-05-01 |

| 4 | 6160086500000357100 | F4A11Z71E7DHUFh99999 | 500 | 2026-02-01 |

| 5 | 6160086500000357100 | F4A11Z71E7DHUFh99999 | 800 | 2026-04-01 |

+----+---------------------+---------------------+---------------+-------------+

```


步骤 1:PARTITION BY 分组

```

按 (uid, activity_account_code) 分组:

组 1: uid = 6160086500000357300, code = F4A11Z71E7DHUFh99999

├─ 记录 1: 2026-01-01, after_value=1000

├─ 记录 2: 2026-03-01, after_value=1500

└─ 记录 3: 2026-05-01, after_value=2000

组 2: uid = 6160086500000357100, code = F4A11Z71E7DHUFh99999

├─ 记录 4: 2026-02-01, after_value=500

└─ 记录 5: 2026-04-01, after_value=800

```


步骤 2:ORDER BY 组内排序

```

每组内按 create_time DESC(降序)排序:

组 1 (uid=...300):

记录 3: 2026-05-01 ← 最新

记录 2: 2026-03-01

记录 1: 2026-01-01 ← 最旧

组 2 (uid=...100):

记录 5: 2026-04-01 ← 最新

记录 4: 2026-02-01 ← 最旧

```


步骤 3:ROW_NUMBER() 分配行号

```

从 1 开始给每组分配行号:

组 1:

记录 3: 2026-05-01, after_value=2000 → rn=1 ← 第 1 名(最新)

记录 2: 2026-03-01, after_value=1500 → rn=2

记录 1: 2026-01-01, after_value=1000 → rn=3

组 2:

记录 5: 2026-04-01, after_value=800 → rn=1 ← 第 1 名(最新)

记录 4: 2026-02-01, after_value=500 → rn=2

```


步骤 4:外层 WHERE rn = 1 过滤

```

只保留 rn=1 的记录(每组的最新一条):

+----+---------------------+---------------------+---------------+-------------+----+

| id | uid | activity_account_code | after_value | create_time | rn |

+----+---------------------+---------------------+---------------+-------------+----+

| 3 | 6160086500000357300 | F4A11Z71E7DHUFh99999 | 2000 | 2026-05-01 | 1 |

| 5 | 6160086500000357100 | F4A11Z71E7DHUFh99999 | 800 | 2026-04-01 | 1 |

+----+---------------------+---------------------+---------------+-------------+----+

最终结果:每个用户的最新余额

  • 用户 ...300 的最新余额:2000

  • 用户 ...100 的最新余额:800

```


四、完整执行流程可视化

```

┌─────────────────────────────────────────────────────────┐

│ 原始数据(5 条记录) │

└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐

│ 步骤 1: PARTITION BY 分组 │

│ 组 1: uid=...300 (3 条记录) │

│ 组 2: uid=...100 (2 条记录) │

└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐

│ 步骤 2: ORDER BY create_time DESC 组内排序 │

│ 组 1: [05-01, 03-01, 01-01] │

│ 组 2: [04-01, 02-01] │

└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐

│ 步骤 3: ROW_NUMBER() 分配行号 │

│ 组 1: [rn=1, rn=2, rn=3] │

│ 组 2: [rn=1, rn=2] │

└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐

│ 步骤 4: WHERE rn=1 过滤 │

│ 结果:2 条记录(每组的第 1 名) │

└─────────────────────────────────────────────────────────┘

```


五、常用窗口函数对比

| 函数 | 作用 | 示例 | 结果 |

|------|------|------|------|

| **ROW_NUMBER()** | 连续行号,不重复 | 1, 2, 3, 4, 5 | 每行唯一 |

| **RANK()** | 跳跃排名,有并列 | 1, 2, 2, 4, 5 | 并列第 2,跳过 3 |

| **DENSE_RANK()** | 密集排名,无跳跃 | 1, 2, 2, 3, 4 | 并列第 2,继续 3 |

| **NTILE(n)** | 分成 n 组 | 1, 1, 2, 2, 3 | 分成 3 组 |


示例对比

```sql

-- 数据:4 个学生成绩

姓名 分数

张三 100

李四 100

王五 90

赵六 80

-- ROW_NUMBER()

张三:1, 李四:2, 王五:3, 赵六:4 ← 强制区分名次

-- RANK()

张三:1, 李四:1, 王五:3, 赵六:4 ← 并列第 1,跳过第 2

-- DENSE_RANK()

张三:1, 李四:1, 王五:2, 赵六:3 ← 并列第 1,继续第 2

```


六、实际应用场景

场景 1:获取每个用户的最新订单

```sql

SELECT order_id, user_id, amount, order_date

FROM (

SELECT *,

ROW_NUMBER() OVER (

PARTITION BY user_id

ORDER BY order_date DESC

) AS rn

FROM orders

) ranked

WHERE rn = 1;

```


场景 2:获取每个部门工资最高的员工

```sql

SELECT employee_name, department, salary

FROM (

SELECT *,

ROW_NUMBER() OVER (

PARTITION BY department

ORDER BY salary DESC

) AS rn

FROM employees

) ranked

WHERE rn = 1;

```


场景 3:分页查询(每页 10 条)

```sql

SELECT * FROM (

SELECT *,

ROW_NUMBER() OVER (ORDER BY create_time DESC) AS rn

FROM articles

) ranked

WHERE rn BETWEEN 1 AND 10; -- 第 1 页

WHERE rn BETWEEN 11 AND 20; -- 第 2 页

```


七、你的 SQL 业务含义

```sql

-- 业务目标:查询指定用户在指定时间前的最新余额

SELECT id, uid, after_value

FROM (

SELECT id, uid, after_value, create_time,

-- 核心逻辑:

-- 1. 按用户 + 账户分组

-- 2. 组内按时间降序排列

-- 3. 给每条记录分配行号(最新=1)

ROW_NUMBER() OVER (

PARTITION BY uid, activity_account_code

ORDER BY create_time DESC

) AS rn

FROM activity_account_flow_0

WHERE activity_account_code = 'F4A11Z71E7DHUFh99999'

AND uid IN (6160086500000357300, 6160086500000357100)

AND create_time <= '2026-05-30 00:00:00'

) ranked

WHERE rn = 1; -- 只保留最新的记录

-- 最终结果:

-- 用户 6160086500000357300 的最新余额

-- 用户 6160086500000357100 的最新余额

```


八、与传统方案的对比

传统方案(GROUP BY + MAX)

```sql

-- 方案 1:子查询

SELECT a.* FROM activity_account_flow a

INNER JOIN (

SELECT uid, MAX(create_time) AS max_time

FROM activity_account_flow

WHERE ...

GROUP BY uid

) b ON a.uid = b.uid AND a.create_time = b.max_time;

-- 缺点:

-- 1. 需要自 JOIN

-- 2. 如果同一时间有多条记录,会返回多条

-- 3. 性能较差

```

窗口函数方案

```sql

-- 方案 2:窗口函数

SELECT id, uid, after_value

FROM (

SELECT *, ROW_NUMBER() OVER (...) AS rn

FROM activity_account_flow

WHERE ...

) ranked

WHERE rn = 1;

-- 优点:

-- 1. 代码简洁

-- 2. 保证每组只返回 1 条

-- 3. 性能更好(MySQL 8.0+)

```


九、注意事项

1. **同一时间多条记录**

```sql

-- 如果同一用户同一时间有多条记录:

-- ROW_NUMBER() → 随机选一条(rn=1)

-- RANK() → 都会标记为 rn=1(返回多条)

-- 根据业务选择:

-- 需要严格 1 条 → ROW_NUMBER()

-- 需要所有并列 → RANK()

```

2. **性能考虑**

```

数据量 < 10 万:窗口函数性能优秀

数据量 > 100 万:需要添加索引

索引建议:(uid, activity_account_code, create_time DESC)

```

3. **MySQL 版本**

```

MySQL 8.0+:支持窗口函数

MySQL 5.7:不支持,需用 JOIN 替代

```


总结

**`ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` 的含义**:

```

  1. PARTITION BY → 分组(类似班级)

  2. ORDER BY → 组内排序(类似成绩排名)

  3. ROW_NUMBER() → 分配行号(第 1 名、第 2 名...)

  4. WHERE rn=1 → 取每组第 1 名(最新记录)

相关推荐
大江东去浪淘尽千古风流人物2 小时前
【Basalt】 VIO(sqrt_keypoint_vio)主流程measure函数梳理
数据库·人工智能·python·机器学习·oracle
空空kkk2 小时前
MySQL 主从同步
android·数据库·mysql
土土哥V_araolin2 小时前
2+1链动退休模式系统(升级版)解析
大数据·小程序·零售
jnrjian2 小时前
RAC archivelog 在共享盘下就可以在一个node进行备份
数据库·sql
瑶山2 小时前
SpringBoot + MongoDB 5分钟快速集成:从0到1实操指南
java·数据库·spring boot·后端·mongodb
linux修理工2 小时前
Claude API 密钥更换方法
java·数据库·mysql
Eternity_GQM2 小时前
【Git入门】
大数据·git·elasticsearch
chushiyunen2 小时前
langchain的流式事件监听astream_event()、todo运行机制
java·数据库·langchain
ManageEngineITSM2 小时前
功能越来越强,但 IT 使用体验却越来越差
大数据·excel·资产管理·itsm·工单系统