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 ...)` 的含义**:
```
-
PARTITION BY → 分组(类似班级)
-
ORDER BY → 组内排序(类似成绩排名)
-
ROW_NUMBER() → 分配行号(第 1 名、第 2 名...)
-
WHERE rn=1 → 取每组第 1 名(最新记录)