☰
SQL 基础综合练习:学生成绩表与订单表 30 题
2026/10/3 12:38:43 网站建设 项目流程

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

这篇用学生成绩表和订单表两条主线、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–90AND+BETWEEN
4查数学或英语课程IN
5查姓名中带"张"LIKE模糊匹配
6查成绩为空的记录IS NULL

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

SELECTstudent_name,course_name,scoreFROMstudent_score;

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

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

SELECT*FROMstudent_scoreWHEREscore>=90;

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

SELECTstudent_name,course_name,scoreFROMstudent_scoreWHEREclass_name='一班'ANDscoreBETWEEN80AND90;

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

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

SELECT*FROMstudent_scoreWHEREcourse_nameIN('数学','英语');

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

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

SELECT*FROMstudent_scoreWHEREstudent_nameLIKE'%张%';

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

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

SELECT*FROMstudent_scoreWHEREscoreISNULL;

错误:<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 题:按成绩从高到低查询

SELECTstudent_name,course_name,scoreFROMstudent_scoreORDERBYscoreDESC;

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

SELECTclass_name,student_name,scoreFROMstudent_scoreORDERBYclass_nameASC,scoreDESC;

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

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

SELECTDISTINCTcourse_nameFROMstudent_score;

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

SELECTCOUNT(*)AStotal_rowsFROMstudent_score;

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

SELECTAVG(score)ASavg_score,MAX(score)ASmax_score,MIN(score)ASmin_scoreFROMstudent_scoreWHEREcourse_name='数学';

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

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

SELECTCOUNT(DISTINCTcustomer_id)AScustomer_countFROMorders;

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

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

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

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

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

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

SELECTclass_name,COUNT(*)ASstudent_countFROMstudent_scoreGROUPBYclass_name;

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

SELECTcourse_name,AVG(score)ASavg_scoreFROMstudent_scoreGROUPBYcourse_name;

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

SELECTclass_name,course_name,AVG(score)ASavg_scoreFROMstudent_scoreGROUPBYclass_name,course_name;

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

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

SELECTcourse_name,AVG(score)ASavg_scoreFROMstudent_scoreGROUPBYcourse_nameHAVINGAVG(score)>85;

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

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

SELECTcustomer_id,customer_name,SUM(quantity*price)AStotal_amountFROMordersGROUPBYcustomer_id,customer_name;

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

SELECTcustomer_id,customer_name,SUM(quantity*price)AStotal_amountFROMordersGROUPBYcustomer_id,customer_nameHAVINGSUM(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 题:查询学生姓名、课程、成绩并显示班级

SELECTstudent_name,class_name,course_name,scoreFROMstudent_score;

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

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

SELECTorder_id,customer_name,product_name,quantity,priceFROMorders;

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

SELECTs.student_name,s.class_name,c.teacher_nameFROMstudent_score sJOINclass_info cONs.class_name=c.class_name;

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

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

SELECTcustomer_id,customer_nameFROMcustomersWHEREcustomer_idNOTIN(SELECTDISTINCTcustomer_idFROMorders);

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

SELECTc.customer_id,c.customer_nameFROMcustomers cWHERENOTEXISTS(SELECT1FROMorders oWHEREo.customer_id=c.customer_id);

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

SELECT*FROMordersWHEREquantity*price=(SELECTMAX(quantity*price)FROMorders);

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

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

SELECTo.customer_id,o.customer_name,SUM(o.quantity*o.price)AStotal_amountFROMorders oGROUPBYo.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 分

UPDATEstudent_scoreSETscore=score+5WHEREcourse_name='数学'ANDscore<60;

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

DELETEFROMstudent_scoreWHEREscoreISNULL;

我见过最疼的事故:<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 题:新建一张学生成绩表

CREATETABLEstudent_score(student_idINT,student_nameVARCHAR(50),class_nameVARCHAR(50),course_nameVARCHAR(50),scoreDECIMAL(5,2),exam_dateDATE);

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

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

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

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

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

SELECTcity,SUM(quantity*price)AStotal_amountFROMordersGROUPBYcityORDERBYtotal_amountDESC;

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

SELECTcustomer_id,customer_name,SUM(quantity*price)AStotal_amountFROMordersGROUPBYcustomer_id,customer_nameORDERBYtotal_amountDESCLIMIT3;

备注:<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判断
谓词PredicateWHERE 后返回真/假的条件表达式
聚合函数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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询