FROM 决定从哪些表拿数据 (第 1、4 课)
↓ JOIN 怎么把表拼宽
WHERE 决定保留哪些行 (第 3 课)
↓ DISTINCT 去重 (第 2 课)
GROUP BY 把行分堆 (第 5 课)
HAVING 决定保留哪些堆
SELECT 决定输出哪些列、叫什么名 (第 2 课)
ORDER BY 决定输出顺序
LIMIT 决定输出几行
第 6-9 课(写数据、表设计、索引、事务)则保证这条流水线跑在正确、快速、安全的表上。
跟着做
步骤 0:复原数据
-- 先回第一课重跑完整建库脚本,把练习库恢复到初始状态
USE sql_practice;
以下 10 题按难度分三关。先自己写,写不出来再看参考答案——答案就在题下,考验定力。
第一关:单表(热身)
Q1. 查所有已发布课程,按价格从高到低。
SELECT title, price
FROM course
WHERE status = 'published'
ORDER BY price DESC;
Q2. 查深圳、杭州两地的 VIP 学生姓名与城市。
SELECT name, city
FROM student
WHERE city IN ('深圳', '杭州') AND vip = 1;
-- 老陈(深圳)1 行
Q3. 统计每个分类的课程数与平均售价,平均价保留两位小数。
SELECT category,
COUNT(*) AS course_cnt,
ROUND(AVG(price), 2) AS avg_price
FROM course
GROUP BY category;
第二关:联表(主力战场)
Q4. 查「SQL 与关系型数据库」的已发布课时,按课内顺序排列。
SELECT l.order_index, l.title
FROM lesson l
JOIN course c ON l.course_id = c.id
WHERE c.slug = 'sql-essentials' AND l.status = 'published'
ORDER BY l.order_index;
-- 3 行(id=9 的 JOIN 课是 draft,被筛掉)
Q5. 查每个学生完成的课时数,没学过的也要出现(计 0)。
SELECT s.name, COUNT(r.id) AS done_cnt
FROM student s
LEFT JOIN study_record r ON r.student_id = s.id AND r.completed = 1
GROUP BY s.id, s.name
ORDER BY done_cnt DESC;
-- 老陈/阿珍/老周 4,小林 2,大鹏/小夏 1,小吴 0
SELECT c.title
FROM course c
LEFT JOIN pay_order o ON o.course_id = c.id
WHERE o.id IS NULL;
-- Java 面向对象进阶 / SQL 与关系型数据库 / Redis 缓存入门 / Git 团队协作基础
反连接套路:LEFT JOIN + 右表主键 IS NULL。
Q7. 查平均分最高、且至少 2 人完成的已发布课时。
SELECT l.id, l.title,
ROUND(AVG(r.score), 2) AS avg_score,
COUNT(*) AS ppl
FROM study_record r
JOIN lesson l ON r.lesson_id = l.id
WHERE r.completed = 1 AND r.score IS NOT NULL
GROUP BY l.id, l.title
HAVING COUNT(*) >= 2
ORDER BY avg_score DESC
LIMIT 1;
-- 关系型数据库与 SQL 是什么,88.75,4 人
第三关:进阶(面试同款)
Q8. 查每个分类售价最高的课程(窗口函数)。
SELECT category, title, price
FROM (
SELECT c.category, c.title, c.price,
ROW_NUMBER() OVER (PARTITION BY c.category ORDER BY c.price DESC) AS rn
FROM course c
) t
WHERE rn = 1;
-- java: Java 面向对象进阶 / database: MySQL 性能优化实战 / frontend: 前端页面基础
GROUP BY 做不了「组内取整行」,这是窗口函数的地盘。内层子查询必须有别名 t(MySQL 强制)。
Q9. 查各课程的实际收入(只算未退款订单),按收入降序。
SELECT c.title,
SUM(o.amount) AS revenue,
COUNT(*) AS paid_orders
FROM pay_order o
JOIN course c ON o.course_id = c.id
WHERE o.status = 'PAID'
GROUP BY c.id, c.title
ORDER BY revenue DESC;
Q10. 查阿珍的全部学习明细:学生名、课程名、课时名、成绩。
SELECT s.name, c.title AS course_title, l.title AS lesson_title, r.score
FROM study_record r
JOIN student s ON r.student_id = s.id
JOIN lesson l ON r.lesson_id = l.id
JOIN course c ON l.course_id = c.id
WHERE s.name = '阿珍' AND r.completed = 1
ORDER BY r.score DESC;
纪律先行:对 codeforge 库只跑 SELECT,永远不跑 DML/DDL;加 LIMIT 控制返回量;没有本地库就先 USE codeforge; 试连。
-- ① 语言类课程清单(列表页数据原型)
SELECT slug, title, level, estimated_weeks
FROM cf_track
WHERE category = 'language'
ORDER BY sort_order;
-- ② 某门课的已发布课时目录(课时侧栏数据原型)
SELECT l.order_index, l.title, l.duration
FROM cf_lesson l
JOIN cf_track t ON l.track_id = t.id
WHERE t.slug = 'sql-essentials' AND l.status = 'published'
ORDER BY l.order_index;
-- ③ 某用户在某课程的学习进度(个人中心数据原型)
SELECT u.username, t.title AS track_title,
l.title AS lesson_title, p.completed, p.score, p.completed_at
FROM cf_progress p
JOIN cf_user u ON p.user_id = u.id
JOIN cf_lesson l ON p.lesson_id = l.id
JOIN cf_track t ON l.track_id = t.id
WHERE u.username = '你的用户名'
ORDER BY t.sort_order, l.order_index;
这三条查询,就是页面「课程列表 / 课时侧栏 / 学习进度」背后 Spring Boot 真正发出的 SQL 原型。