云计算百科
云计算领域专业知识百科平台

SQL 基础综合练习:学生成绩表与订单表 30 题

我刚学 SQL 那会,背了两周语法,一句"查成绩为空的学生"就把我卡住——我理所当然地写了 WHERE score = NULL,结果一行都查不到,因为空值(NULL)根本不能用等号比较。

资源链接
SQL 练习文件 点击查看
这篇用学生成绩表和订单表两条主线、30 道递进练习,带你把零散语法练成"看懂需求 → 拆开步骤 → 写稳语句"的固定手感。

一、练习数据:先把两张表摆上桌

后面所有题目都围绕两张业务表展开,字段先对齐。

学生成绩表 student_score

字段含义
student_id 学号
student_name 姓名
class_name 班级
course_name 课程
score 成绩
exam_date 考试日期

订单表 orders

字段含义
order_id 订单号
customer_id 客户编号
customer_name 客户姓名
product_name 商品名称
category 商品类别
quantity 数量
price 单价
order_date 下单日期
city 城市

二、基础查询:先把数据捞准

要分析数据,先得把数据从表里捞出来。可同样是"查",为什么你写的语句要么查多了行,要么漏掉了本该有的记录?

题号业务需求关键写法
1 查所有学生的姓名、课程、成绩 SELECT 投影列
2 查成绩 ≥ 90 的记录 WHERE + 比较符
3 查一班且成绩在 80–90 AND + BETWEEN
4 查数学或英语课程 IN
5 查姓名中带"张" LIKE 模糊匹配
6 查成绩为空的记录 IS NULL

第 1 题:查询所有学生的姓名、课程和成绩

SELECT student_name, course_name, score
FROM student_score;

只取需要的列,不用 SELECT *,避免拖回无关数据。

第 2 题:查询成绩大于等于 90 分的记录

SELECT *
FROM student_score
WHERE score >= 90;

第 3 题:查询班级为"一班"且成绩在 80 到 90 之间的学生

SELECT student_name, course_name, score
FROM student_score
WHERE class_name = '一班'
AND score BETWEEN 80 AND 90;

BETWEEN … AND … 包含两端边界,写成 > 80 AND < 90 会把 80 和 90 漏掉。

第 4 题:查询课程为"数学"或"英语"的成绩记录

SELECT *
FROM student_score
WHERE course_name IN ('数学', '英语');

枚举离散值用 IN,比一连串 OR 更清爽。

第 5 题:查询姓名中包含"张"的学生

SELECT *
FROM student_score
WHERE student_name LIKE '%张%';

% 代表任意多个字符,_ 代表单个字符。

第 6 题:查询成绩为空的学生记录

SELECT *
FROM student_score
WHERE score IS NULL;

错误:<font style="color:#c00;">WHERE score = NULL</font> 一行都查不到,NULL 与任何值的比较结果都不是"真"。

正确:判空只能用 <font style="color:#080;">IS NULL</font>,非空用 <font style="color:#080;">IS NOT NULL</font>。

三、排序聚合:从逐行记录到一个汇总数字

数据捞出来了,可一堆无序行没法看,需求还想要"数学平均分"“多少个独立客户”。从逐行记录跨到一个汇总数字,这一步怎么写?

题号业务需求关键写法
7 成绩从高到低排列 ORDER BY … DESC
8 班级升序、成绩降序 多字段排序
9 列出所有出现过的课程 DISTINCT
10 统计总记录数 COUNT(*)
11 数学的平均/最高/最低分 AVG/MAX/MIN
12 统计不同客户的数量 COUNT(DISTINCT …)

第 7 题:按成绩从高到低查询

SELECT student_name, course_name, score
FROM student_score
ORDER BY score DESC;

第 8 题:按班级升序、成绩降序排列

SELECT class_name, student_name, score
FROM student_score
ORDER BY class_name ASC, score DESC;

前一个字段值相同,才会轮到后一个字段排序。

第 9 题:查询所有出现过的课程名称并去重

SELECT DISTINCT course_name
FROM student_score;

第 10 题:统计成绩表的总记录数

SELECT COUNT(*) AS total_rows
FROM student_score;

第 11 题:统计数学课程的平均分、最高分、最低分

SELECT AVG(score) AS avg_score,
MAX(score) AS max_score,
MIN(score) AS min_score
FROM student_score
WHERE course_name = '数学';

COUNT(*) 统计全部行数,COUNT(列) 只统计该列非空的行数;聚合函数会自动忽略空值。

第 12 题:统计订单表中不同客户的数量

SELECT COUNT(DISTINCT customer_id) AS customer_count
FROM orders;

去重后再计数,这正是"独立客户数"的标准写法。

注意:先过滤、再聚合,WHERE 永远跑在聚合函数前面。

四、分组统计:一行 SQL 算出多组结果

全局一个平均分好算,可需求是"每个班、每门课、每个客户"各自的数字,怎么让一条语句同时吐出多组结果?

题号业务需求关键写法
13 统计每个班的人数 GROUP BY
14 统计每门课的平均分 分组 + AVG
15 每个班每门课的平均分 多字段分组
16 平均分大于 85 的课程 HAVING
17 每个客户的订单总金额 分组 + SUM
18 总金额超 1000 的客户 分组后过滤

第 13 题:统计每个班级的学生人数

SELECT class_name, COUNT(*) AS student_count
FROM student_score
GROUP BY class_name;

第 14 题:统计每门课程的平均分

SELECT course_name, AVG(score) AS avg_score
FROM student_score
GROUP BY course_name;

第 15 题:统计每个班级每门课程的平均分

SELECT class_name, course_name, AVG(score) AS avg_score
FROM student_score
GROUP BY class_name, course_name;

多字段分组的粒度是字段组合,先按班级、再按课程切分。

第 16 题:查询平均分大于 85 的课程

SELECT course_name, AVG(score) AS avg_score
FROM student_score
GROUP BY course_name
HAVING AVG(score) > 85;

我在这儿栽过:把聚合条件写进 <font style="color:#c00;">WHERE AVG(score) > 85</font>,语句直接报错。

第 17 题:统计每个客户的订单总金额

SELECT customer_id, customer_name,
SUM(quantity * price) AS total_amount
FROM orders
GROUP BY customer_id, customer_name;

第 18 题:查询订单总金额超过 1000 的客户

SELECT customer_id, customer_name,
SUM(quantity * price) AS total_amount
FROM orders
GROUP BY customer_id, customer_name
HAVING SUM(quantity * price) > 1000;

行过滤与组过滤到底差在哪?看数据库的逻辑执行顺序:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

WHERE 在分组前逐行过滤,手里根本没有聚合结果;HAVING 在分组后执行,专门筛选聚合值。

记住:WHERE 过滤行,HAVING 过滤组,聚合条件只能进 HAVING。

五、连接子查询:跨表拼字段、分步算结果

单表能查的都查完了,可信息散落在多张表,还得回答"谁没下过单""哪单金额最高"这类需要先算一步的问题,这时候怎么办?

题号业务需求关键写法
19 取学生姓名、课程、成绩、班级 单表直查(对比)
20 取订单的客户、商品信息 单表直查(对比)
21 查每个班级的班主任 JOIN … ON
22 查没下过订单的客户 NOT IN 子查询
23 查金额最高的订单 标量子查询
24 查客户总金额并带姓名 分组(连接可扩展)

第 19 题:查询学生姓名、课程、成绩并显示班级

SELECT student_name, class_name, course_name, score
FROM student_score;

字段都在一张表,直接查即可,不必为了连接而连接。

第 20 题:查询每个订单的客户姓名和商品名称

SELECT order_id, customer_name, product_name, quantity, price
FROM orders;

第 21 题:假设有班级表 class_info(class_name, teacher_name),查每个班的班主任

SELECT s.student_name, s.class_name, c.teacher_name
FROM student_score s
JOIN class_info c
ON s.class_name = c.class_name;

连接(JOIN)按关联字段拼表,关联条件写在 ON 之后。我漏写过一次 ON,直接拼出笛卡尔积(Cartesian Product),行数变成两表相乘,测试库当场卡住。

第 22 题:查询没有下过订单的客户

SELECT customer_id, customer_name
FROM customers
WHERE customer_id NOT IN (
SELECT DISTINCT customer_id
FROM orders
);

备注:customers 为客户主表。当子查询结果里混入 NULL 时,<font style="color:#666;">NOT IN</font> 会整体失效;更稳的写法是 <font style="color:#666;">NOT EXISTS</font>。

SELECT c.customer_id, c.customer_name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);

第 23 题:查询订单金额最高的订单信息

SELECT *
FROM orders
WHERE quantity * price = (
SELECT MAX(quantity * price)
FROM orders
);

子查询(Subquery)返回单个值时用 = 比较;返回多个值要用 IN、ANY 或 ALL。

第 24 题:查询每个客户的订单总金额并列出姓名

SELECT o.customer_id,
o.customer_name,
SUM(o.quantity * o.price) AS total_amount
FROM orders o
GROUP BY o.customer_id, o.customer_name;

需要补客户资料时,先按客户聚合,再连接客户表补字段。

注意:连接拼字段,子查询分步算,复杂查询从内层往外写。

六、更新建表:写操作的稳妥落地与 TopN

只读数据不够,你还要改数据、建表、插记录,并给出"消费 Top 3 客户"。写操作一旦出错,后果比查询重得多,怎么稳妥落地?

题号业务需求关键写法
25 数学不及格的成绩加 5 分 UPDATE
26 删除成绩为空的记录 DELETE
27 新建学生成绩表 CREATE TABLE
28 插入一条订单记录 INSERT
29 各城市总金额降序 分组 + 排序
30 总金额前 3 的客户 LIMIT 取 TopN

第 25 题:将数学课程中低于 60 分的成绩统一加 5 分

UPDATE student_score
SET score = score + 5
WHERE course_name = '数学'
AND score < 60;

第 26 题:删除成绩为空的学生记录

DELETE FROM student_score
WHERE score IS NULL;

我见过最疼的事故:<font style="color:#c00;">UPDATE</font>、<font style="color:#c00;">DELETE</font> 漏掉 <font style="color:#c00;">WHERE</font>,整表被改且默认无法撤销。

做法:先把 <font style="color:#080;">WHERE</font> 条件套进 <font style="color:#080;">SELECT</font> 跑一遍,确认命中的行无误,再执行改写。

第 27 题:新建一张学生成绩表

CREATE TABLE student_score (
student_id INT,
student_name VARCHAR(50),
class_name VARCHAR(50),
course_name VARCHAR(50),
score DECIMAL(5,2),
exam_date DATE
);

整数用 INT、字符串用 VARCHAR、小数用 DECIMAL、日期用 DATE。

第 28 题:向订单表插入一条订单记录

INSERT INTO orders (
order_id, customer_id, customer_name,
product_name, category, quantity,
price, order_date, city
) VALUES (
1001, 1, '张三',
'笔记本', '数码', 2,
5999.00, '2024-06-01', '北京'
);

字段顺序与值顺序必须一一对应。

第 29 题:查询每个城市的订单总金额,并按金额降序

SELECT city, SUM(quantity * price) AS total_amount
FROM orders
GROUP BY city
ORDER BY total_amount DESC;

第 30 题:查询订单总金额排名前 3 的客户

SELECT customer_id, customer_name,
SUM(quantity * price) AS total_amount
FROM orders
GROUP BY customer_id, customer_name
ORDER BY total_amount DESC
LIMIT 3;

备注:<font style="color:#666;">LIMIT</font> 为 MySQL/PostgreSQL 写法;SQL Server 用 <font style="color:#666;">TOP</font>,Oracle 用 <font style="color:#666;">FETCH FIRST n ROWS ONLY</font>。

注意:先查后改,UPDATE/DELETE 不带 WHERE 就是事故。

七、总结

30 题走下来,一条主线很清楚:学生成绩表练的是过滤、聚合与分组,订单表练的是业务计算、连接与排序,写操作则逼着你养成"先查后改"的习惯。

想把手感固化,再做三件事:

  • 把成绩改成 59、60、61 这类边界值重跑,体会 BETWEEN 与 > 的差异;
  • 同一题用 NOT IN、NOT EXISTS、LEFT JOIN 三种写法各写一遍;
  • 每条 SQL 都按执行顺序复盘一次,说清每一步的先后。

语法记不住可以随时查,思路错了才会步步错。把这条递进链路练熟,再去碰窗口函数、CTE 和索引优化,会顺很多。

术语速查表

术语英文 / 关键字含义
空值 NULL 未知或缺失值,只能用 IS NULL 判断
谓词 Predicate WHERE 后返回真/假的条件表达式
聚合函数 Aggregate Function 对一组行计算出单个值
分组 GROUP BY 按字段把数据划分为多个组
连接 JOIN 按关联条件拼合多张表
子查询 Subquery 嵌套在另一条语句中的查询
笛卡尔积 Cartesian Product 多表无条件配对,行数相乘
别名 Alias 用 AS 给表或列起的临时名
分页 Pagination 用 LIMIT 分段返回结果

参考链接

  • MySQL 8.0 Reference Manual — SELECT Statement
  • MySQL 8.0 Reference Manual — Aggregate Functions
  • MySQL 8.0 Reference Manual — JOIN Syntax
  • MySQL 8.0 Reference Manual — UPDATE Syntax
赞(0)
未经允许不得转载:网硕互联帮助中心 » SQL 基础综合练习:学生成绩表与订单表 30 题
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!