Skip to content

Commit 5562961

Browse files
committed
Merge branch '2.0'
2 parents 6a78f4a + 87a8a1d commit 5562961

33 files changed

+4614
-130
lines changed

.github/workflows/docs.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
docs:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
# 使用 git clone 替代 actions/checkout(适配仅允许组织内 actions 的策略)
17+
- name: 获取代码
18+
run: |
19+
git clone --branch ${{ github.ref_name }} https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git .
20+
21+
- name: 设置 pnpm
22+
run: |
23+
corepack enable
24+
corepack prepare pnpm@9 --activate
25+
26+
# ubuntu-latest 自带 Node.js 20,如需 Node 22 可取消下行注释
27+
# - run: npx -y n 22 && npm cache clean -f
28+
- name: 安装依赖
29+
run: pnpm install --frozen-lockfile
30+
31+
- name: 构建 VuePress 站点
32+
run: pnpm docs:build
33+
34+
# 使用 git 命令替代 crazy-max/ghaction-github-pages
35+
- name: 部署到 GitHub Pages
36+
run: |
37+
cd docs/.vuepress/dist
38+
git init
39+
git config user.name "github-actions[bot]"
40+
git config user.email "github-actions[bot]@users.noreply.github.com"
41+
git add -A
42+
git commit -m "Deploy from GitHub Actions"
43+
git branch -M main
44+
git push -f https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git main:gh-pages

docs/.vuepress/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { plumeTheme } from 'vuepress-theme-plume'
66
export default defineUserConfig({
77
base: '/',
88
lang: 'zh-CN',
9-
title: '云岱筑码',
10-
description: '专注技术深耕与实战沉淀的开发者札记',
9+
title: '云泽汇码',
10+
description: '专注技术深耕与实战沉淀的开发者笔记',
1111

1212
head: [
1313
// 配置站点图标

docs/.vuepress/navbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const navbar = defineNavbarConfig([
1010
text: '笔记',
1111
items: [
1212
{ text: '考研', link: '/notes/postgraduate/README.md' }
13-
, { text: '赚钱', link: '/notes/random/README.md' }
13+
// , { text: '赚钱', link: '/notes/random/README.md' }
1414
, { text: '友情链接', link: '/other/friends.md' }
1515
]
1616
},

docs/.vuepress/notes.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@ const interviewNote = defineNoteConfig({
1818
sidebar: 'auto'
1919
})
2020

21-
const makeMoneyNote = defineNoteConfig({
22-
dir: 'random',
23-
link: '/random',
24-
sidebar: ['', '黄金交易'],
25-
})
26-
2721
export const notes = defineNotesConfig({
2822
dir: 'notes',
2923
link: '/',
30-
notes: [postgraduateNote, interviewNote, makeMoneyNote],
24+
notes: [postgraduateNote, interviewNote],
3125
})

docs/.vuepress/plume.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export default defineThemeConfig({
4141
*/
4242
profile: {
4343
avatar: 'https://blog.gaohan.asia/a/Snipaste_2025-03-06_14-35-40.png',
44-
name: '云岱',
45-
description: '专注技术深耕与实战沉淀的开发者札记',
44+
name: '云泽',
45+
description: '专注技术深耕与实战沉淀的开发者笔记',
4646
// circle: true,
4747
// location: '',
4848
// organization: '',

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ config:
77
full: true
88
background: tint-plate
99
hero:
10-
name: 云岱筑码
10+
name: 云泽汇码
1111
tagline:
12-
text: 专注技术深耕与实战沉淀的开发者手记,记录成长的每一个足迹。
12+
text: 专注技术深耕与实战沉淀的开发者笔记,记录成长的每一个足迹。
1313
actions:
1414
-
1515
theme: brand
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
title: 窗口函数ROW_NUMBER
3+
createTime: 2026/01/04 19:16:02
4+
permalink: /article/nawr706a/
5+
tags:
6+
- 数据库
7+
- MySQL
8+
---
9+
ROW_NUMBER() OVER实战:优雅实现分组取最新记录,一行代码搞定学生最新成绩查询。
10+
11+
<!-- more -->
12+
13+
14+
15+
作为一名班主任,你是不是经常需要查看每个学生最近一次考试的成绩?今天要介绍的SQL窗口函数,就能帮你轻松解决这个问题!
16+
17+
## 真实的教学场景
18+
19+
假设我们是一所学校的教务系统管理员,需要为每个班级的**每个学生**获取他们**最近一次考试的成绩**
20+
21+
我们有三个主要数据表:
22+
1. **学生表**:记录学生基本信息
23+
2. **考试成绩表**:记录每次考试的成绩
24+
3. **班级信息表**:记录班级状态
25+
26+
## 数据表结构(简化)
27+
28+
```sql
29+
-- 学生表
30+
CREATE TABLE students (
31+
id INT PRIMARY KEY, -- 学生ID
32+
student_no VARCHAR(20), -- 学号
33+
class_id INT, -- 班级ID
34+
student_name VARCHAR(50) -- 学生姓名
35+
);
36+
37+
-- 考试成绩表
38+
CREATE TABLE exam_scores (
39+
id INT PRIMARY KEY, -- 记录ID
40+
student_id INT, -- 学生ID
41+
exam_date DATE, -- 考试日期
42+
subject VARCHAR(50), -- 科目
43+
score INT, -- 分数
44+
update_time DATETIME -- 更新时间
45+
);
46+
47+
-- 班级表
48+
CREATE TABLE classes (
49+
id INT PRIMARY KEY, -- 班级ID
50+
class_name VARCHAR(50), -- 班级名称
51+
is_active TINYINT(1) -- 是否活跃
52+
);
53+
```
54+
55+
## 解决方案:窗口函数
56+
57+
```sql
58+
-- 获取每个学生最近一次考试的成绩
59+
SELECT student_id, student_no, class_name, subject, score, exam_date
60+
FROM (
61+
SELECT
62+
s.id AS student_id,
63+
s.student_no,
64+
s.student_name,
65+
c.class_name,
66+
es.subject,
67+
es.score,
68+
es.exam_date,
69+
es.update_time,
70+
-- 核心魔法在这里!
71+
ROW_NUMBER() OVER (
72+
PARTITION BY s.id -- 按学生ID分组
73+
ORDER BY es.exam_date DESC -- 按考试日期降序排列
74+
) AS row_num
75+
FROM students s
76+
INNER JOIN exam_scores es ON s.id = es.student_id
77+
LEFT JOIN classes c ON s.class_id = c.id AND c.is_active = 1
78+
WHERE s.student_no IN ('2023001', '2023002', '2023003')
79+
) temp_table
80+
WHERE row_num = 1 -- 只取每个学生的第一条(最新的)记录
81+
ORDER BY class_name, student_no;
82+
```
83+
84+
## 逐步拆解这个"魔法"
85+
86+
### 第一步:理解数据
87+
假设学生张三的考试记录:
88+
```
89+
ID: 2023001, 张三的考试记录:
90+
1. 2023-09-01 数学 85分
91+
2. 2023-10-08 数学 90分 ← 最新的
92+
3. 2023-08-20 数学 78分
93+
```
94+
95+
### 第二步:窗口函数执行过程
96+
97+
```sql
98+
ROW_NUMBER() OVER (
99+
PARTITION BY s.id -- 对每个学生单独编号
100+
ORDER BY es.exam_date DESC -- 按考试日期从新到旧排序
101+
) AS row_num
102+
```
103+
104+
执行结果会是:
105+
```
106+
学生2023001(张三):
107+
考试日期 科目 分数 row_num
108+
2023-10-08 数学 90 1 ← 最新的一次
109+
2023-09-01 数学 85 2
110+
2023-08-20 数学 78 3
111+
112+
学生2023002(李四):
113+
考试日期 科目 分数 row_num
114+
2023-10-05 英语 92 1 ← 最新的一次
115+
2023-09-02 英语 88 2
116+
```
117+
118+
### 第三步:筛选最新记录
119+
```sql
120+
WHERE row_num = 1
121+
```
122+
这样就只保留了每个学生最近的那次考试成绩。
123+
124+
## 实际应用场景
125+
126+
### 场景1:成绩单打印
127+
```sql
128+
-- 打印高一一班所有学生最近一次数学考试成绩
129+
WHERE c.class_name = '高一一班'
130+
AND es.subject = '数学'
131+
AND row_num = 1
132+
```
133+
134+
### 场景2:进步奖评选
135+
```sql
136+
-- 找出每个学生最近两次考试,计算进步情况
137+
SELECT * FROM (
138+
SELECT ...,
139+
ROW_NUMBER() OVER (
140+
PARTITION BY student_id
141+
ORDER BY exam_date DESC
142+
) AS row_num
143+
...
144+
) WHERE row_num <= 2 -- 取最近两次考试
145+
```
146+
147+
## 传统方法的对比
148+
149+
### 传统方法(子查询):
150+
```sql
151+
-- 复杂且效率低
152+
SELECT s.*, es1.*
153+
FROM students s
154+
INNER JOIN exam_scores es1 ON s.id = es1.student_id
155+
INNER JOIN (
156+
SELECT student_id, MAX(exam_date) as latest_date
157+
FROM exam_scores
158+
GROUP BY student_id
159+
) es2 ON es1.student_id = es2.student_id
160+
AND es1.exam_date = es2.latest_date
161+
```
162+
163+
### 窗口函数方法:
164+
- **代码简洁**:逻辑一目了然
165+
- **性能更优**:通常执行效率更高
166+
- **扩展性强**:轻松调整取第N条记录
167+
168+
## 窗口函数其他妙用
169+
170+
### 1. 排名功能
171+
```sql
172+
-- 每个班级内按成绩排名
173+
RANK() OVER (
174+
PARTITION BY class_id
175+
ORDER BY score DESC
176+
) AS class_rank
177+
```
178+
179+
### 2. 计算平均值
180+
```sql
181+
-- 计算每个学生与班级平均分的差距
182+
AVG(score) OVER (
183+
PARTITION BY class_id
184+
) AS class_avg_score
185+
```
186+
187+
### 3. 累计计算
188+
```sql
189+
-- 计算每个学生成绩的累计和
190+
SUM(score) OVER (
191+
PARTITION BY student_id
192+
ORDER BY exam_date
193+
) AS cumulative_score
194+
```
195+
196+
## 最佳实践建议
197+
198+
1. **索引优化**:确保`exam_date`字段有索引
199+
2. **分区字段**:选择合适的分区字段,避免数据倾斜
200+
3. **排序字段**:使用有索引的字段排序提升性能
201+
4. **结果验证**:先用小数据量测试,确保逻辑正确
202+
203+
## 总结
204+
205+
通过`ROW_NUMBER() OVER`窗口函数,我们可以轻松解决"每组取最新/最老记录"这类常见需求。就像老师快速找出每个学生最新成绩一样简单!
206+
207+
**关键记住三点**
208+
1. `PARTITION BY`:告诉SQL如何分组
209+
2. `ORDER BY DESC`:告诉SQL如何排序(DESC取最新)
210+
3. `WHERE row_num = 1`:告诉SQL只要每组第一条

0 commit comments

Comments
 (0)