用聚合函数统计成绩和课时,按学生、课程分组统计,再对结果排序,取出前几名。
MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。
Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。
输入法切到英文。表名、字段名不加引号,字符串值才用单引号。
与实验一是同一份数据。之前没有修改过数据可以不再执行,不确定就再执行一次。
-- 第 6 章实验数据:教材 teaching 库的五张表和教材中的数据 -- 四次实验共用这一份数据。整段执行一次即可,数据改乱了重新执行就能恢复。 CREATE DATABASE IF NOT EXISTS teaching DEFAULT CHARACTER SET utf8mb4; USE teaching; -- 删除旧的表。先暂时关闭外键检查,库里有别的表引用它们时也能删除 SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS sc, tc, avgsal, s, c, t; SET FOREIGN_KEY_CHECKS = 1; -- 课程表 CREATE TABLE c ( cno CHAR(4) NOT NULL, cn VARCHAR(30) NOT NULL, ct INT, PRIMARY KEY (cno) ); -- 学生表 CREATE TABLE s ( sno CHAR(4) NOT NULL, sn VARCHAR(20) NOT NULL, sex CHAR(2), age INT, maj VARCHAR(30), dept VARCHAR(30), PRIMARY KEY (sno) ); -- 教师表 CREATE TABLE t ( tno CHAR(4) NOT NULL, tn VARCHAR(20) NOT NULL, sex CHAR(2), age INT, prof VARCHAR(20), maj VARCHAR(30), dept VARCHAR(30), sal DECIMAL(8,2), PRIMARY KEY (tno) ); -- 选课表 CREATE TABLE sc ( sno CHAR(4) NOT NULL, cno CHAR(4) NOT NULL, score DECIMAL(5,2), PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES s (sno), FOREIGN KEY (cno) REFERENCES c (cno) ); -- 授课表 CREATE TABLE tc ( tno CHAR(4) NOT NULL, cno CHAR(4) NOT NULL, tcdate DATE, PRIMARY KEY (tno, cno), FOREIGN KEY (tno) REFERENCES t (tno), FOREIGN KEY (cno) REFERENCES c (cno) ); INSERT INTO c VALUES ('c1','Java程序设计',40), ('c2','程序设计基础',48), ('c3','线性代数',48), ('c4','数据结构',64), ('c5','数据库系统',56), ('c6','数据挖掘',32), ('c7','高等数学',60), ('c8','控制理论',32); INSERT INTO s VALUES ('s1','王彤','女',18,'计算机','信息学院'), ('s2','苏乐','男',20,'信息','信息学院'), ('s3','林欣怡','女',19,'信息','信息学院'), ('s4','陶然','男',19,'自动化','工学院'), ('s5','魏立','男',17,'数学','理学院'), ('s6','何欣荣','女',21,'计算机','信息学院'), ('s7','赵琳琳','女',19,'数学','理学院'), ('s8','李轩','男',18,'自动化','工学院'); INSERT INTO t VALUES ('t1','刘杨','男',38,'教授','计算机','信息学院',3800.00), ('t2','石丽','女',28,'讲师','计算机','信息学院',2800.00), ('t3','顾伟','男',35,'副教授','计算机','信息学院',3200.00), ('t4','赵礼','男',45,'教授','自动化','工学院',4267.90), ('t5','赵希希','女',33,'副教授','数学','理学院',3332.67), ('t6','张刚','男',30,'讲师','计算机','工学院',2600.00); INSERT INTO sc VALUES ('s1','c1',90.50), ('s1','c2',88.00), ('s2','c4',70.00), ('s2','c5',57.00), ('s2','c6',81.50), ('s2','c7',NULL), ('s3','c1',75.00), ('s3','c2',76.50), ('s3','c4',85.00), ('s4','c1',93.00), ('s4','c2',82.00), ('s4','c6',NULL), ('s4','c7',68.00), ('s5','c2',79.00), ('s5','c7',84.00), ('s7','c3',77.00), ('s7','c5',62.00), ('s7','c7',100.00), ('s8','c2',66.00), ('s8','c3',96.00); INSERT INTO tc VALUES ('t1','c1','2021-09-03'), ('t1','c2','2021-09-04'), ('t2','c5','2021-03-06'), ('t2','c6','2021-09-06'), ('t3','c2','2021-03-08'), ('t3','c4','2021-09-08'), ('t4','c3','2021-03-10'), ('t5','c7','2021-09-12'), ('t5','c8','2021-03-15');
SELECT SUM(score) AS 总分, AVG(score) AS 平均分 FROM sc WHERE sno = 's2'; -- 聚合函数的结果还可以继续计算 SELECT MAX(ct) AS 最高课时, MIN(ct) AS 最低课时, MAX(ct) - MIN(ct) AS 课时差 FROM c; -- COUNT(*) 统计行数,COUNT(score) 只统计成绩不为空的行 SELECT COUNT(*) AS 选课门数, COUNT(score) AS 有成绩门数 FROM sc WHERE sno = 's2';
| SUM(score) AVG(score) | 求和、求平均值,成绩为空的行不参加计算 |
| MAX(ct) - MIN(ct) | 聚合函数的结果可以参加运算 |
| COUNT(*) | 统计行数 |
| COUNT(score) | 统计 score 不为空的个数 |
-- 按课程号分组,每组统计行数 SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno; SELECT sno AS 学号, COUNT(*) AS 选课门数, AVG(score) AS 平均分 FROM sc GROUP BY sno; SELECT dept AS 院系, COUNT(*) AS 人数 FROM s GROUP BY dept;
| GROUP BY cno | 课程号相同的行分为一组,每组在结果中占一行 |
| COUNT(*) | 对每一组分别统计行数 |
| SELECT 后面的字段 | 除聚合函数外,只能写 GROUP BY 中出现的字段 |
-- 对组的条件写在 HAVING 中 SELECT sno AS 学号, COUNT(*) AS 选课门数 FROM sc GROUP BY sno HAVING COUNT(*) >= 3; -- WHERE 先逐行去掉不及格和没有成绩的记录,再分组,最后 HAVING 筛组 SELECT sno AS 学号, COUNT(*) AS 及格门数 FROM sc WHERE score >= 60 GROUP BY sno HAVING COUNT(*) >= 3;
| HAVING COUNT(*) >= 3 | 分组之后,只保留行数不少于 3 的组 |
| WHERE score >= 60 | 分组之前逐行筛选,只留下及格的记录 |
| WHERE 与 HAVING | WHERE 筛选行,写在 GROUP BY 前面。HAVING 筛选组,写在后面。把 COUNT 写进 WHERE 会报 1111 |
SELECT sno, cno, score FROM sc WHERE sno = 's2' ORDER BY score DESC; -- 先按课时降序,课时相同时再按课程名降序 SELECT * FROM c ORDER BY ct DESC, cn DESC;
| ORDER BY score DESC | 按成绩降序。DESC 是降序,ASC 或不写是升序 |
| ct DESC, cn DESC | 第一个字段相同时才按第二个字段排序,每个字段后面各写各的 DESC |
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno ORDER BY 选课人数 DESC LIMIT 3; -- 跳过 1 行,取 3 行 SELECT tno, tn, prof FROM t ORDER BY tno LIMIT 1, 3;
| ORDER BY 选课人数 DESC | ORDER BY 中可以使用 SELECT 中起的别名 |
| LIMIT 3 | 排好序以后取前 3 行 |
| LIMIT 1, 3 | 前一个数是跳过的行数,后一个数是返回的行数,也可以写成 LIMIT 3 OFFSET 1 |