第 1 课 语句骨架与执行顺序
先理解 SQL 是"描述结果"的语言,再记住骨架与执行顺序。
SQL 是非过程化语言:只说明"要什么数据",不说明"怎么取"。 一条查询语句由若干子句组成,书写顺序与执行顺序不同——这是初学时最容易混淆的地方。 数据库先根据 FROM 确定数据来源,用 WHERE 过滤行,再按 GROUP BY 分组、HAVING 过滤组, 最后才计算 SELECT 中的表达式,并用 ORDER BY 排序。
SELECT [ALL|DISTINCT] <目标列表达式>[, ...] FROM <表名或视图名>[, ...] [WHERE <行条件>] [GROUP BY <列名> [HAVING <组条件>]] [ORDER BY <列名> [ASC|DESC]]; -- 执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY -- 因此:WHERE 中不能出现聚集函数,SELECT 中的列别名也不能在 WHERE 中使用
- WHERE 作用于分组前的行,HAVING 作用于分组后的组。
- ORDER BY 永远最后执行,所以只有它能使用 SELECT 中定义的别名。
- DISTINCT 作用于整个结果行,不是单个列。
课堂练习(3 题,点击展开参考答案)
- 说明
SELECT AVG(Grade) FROM SC WHERE AVG(Grade) > 80;为什么报错,该怎么改? 答:WHERE 中不允许使用聚集函数。本例不需要分组,直接写SELECT AVG(Grade) FROM SC;;若按课程统计则应写SELECT Cno, AVG(Grade) FROM SC GROUP BY Cno HAVING AVG(Grade) > 80; SELECT Sname AS n FROM Student WHERE n LIKE '刘%';能否执行? 答:不能(多数实现)。别名在 SELECT 阶段生成,而 WHERE 先于 SELECT 执行,应把条件写成WHERE Sname LIKE '刘%'。- ORDER BY 中使用别名是否可行? 答:可以。ORDER BY 在 SELECT 之后执行,能识别 SELECT 中定义的别名。
第 2 课 数据定义:模式、表与索引
对应教材 3.2。定义表就是定义"结构与约束"。
建表时要同时想清楚三件事:有哪些列、每列的数据类型、以及必须满足的完整性约束。 约束可以直接写在列后面(列级),也可以在所有列定义完之后单独写(表级)。 凡涉及多个列的约束(如多属性主码、外码)只能写成表级。
-- 模式(数据库下的命名空间)
CREATE SCHEMA "S-T" AUTHORIZATION WANG;
DROP SCHEMA "S-T" CASCADE; -- CASCADE 连带删除 / RESTRICT 有依赖则拒绝
-- 基本表:列级约束
CREATE TABLE Student (
Sno CHAR(9) PRIMARY KEY, -- 主码:非空且不重复
Sname CHAR(20) UNIQUE,
Ssex CHAR(2) CHECK (Ssex IN ('男','女')),
Sage SMALLINT NOT NULL,
Sdept CHAR(20)
);
-- 基本表:表级约束(多属性主码 + 两个外码)
CREATE TABLE SC (
Sno CHAR(9),
Cno CHAR(4),
Grade SMALLINT CHECK (Grade BETWEEN 0 AND 100),
PRIMARY KEY (Sno, Cno),
FOREIGN KEY (Sno) REFERENCES Student(Sno) ON DELETE CASCADE,
FOREIGN KEY (Cno) REFERENCES Course(Cno)
);
ALTER TABLE Student ADD Sentrance DATE; -- 增加列
ALTER TABLE Student ALTER COLUMN Sage INT; -- 修改类型
DROP TABLE Student CASCADE;
-- 索引(系统自动使用,用户不能显式指定)
CREATE UNIQUE INDEX Stusno ON Student(Sno);
CREATE CLUSTERED INDEX Stusname ON Student(Sname);
DROP INDEX Stusno;
- 常用类型:CHAR(n) 定长、VARCHAR(n) 变长、INT / SMALLINT、DECIMAL(p,d)、DATE / TIME。
- 外码的违约处理:NO ACTION(默认拒绝)、CASCADE(级联)、SET NULL(要求该列允许为空)。
- 索引提高查询速度,但降低插入更新速度;一个表只能有一个聚簇索引。
课堂练习(3 题)
- 为什么
PRIMARY KEY (Sno, Cno)不能写成列级约束? 答:它涉及两个列,列级约束只能约束所在列,必须写成表级约束。 - 删除 Student 中某学生时希望自动删除其选课记录,应如何定义外码?
答:在 SC 表的外码上加
ON DELETE CASCADE。 DROP TABLE Student CASCADE;与RESTRICT的后果有何不同? 答:CASCADE 连带删除依赖该表的视图与外码约束;RESTRICT 在存在依赖时拒绝执行。
第 3 课 单表查询
对应教材 3.3.1。选择列、过滤行、排序、统计分组。
单表查询是所有查询的基础。分成四步理解: ①投影(选哪些列,可含计算列与别名)→ ②选择(WHERE 保留哪些行)→ ③排序(ORDER BY)→ ④统计(聚集函数与 GROUP BY)。 空值用 IS NULL 判断,不能用等号。
-- ①投影与计算列
SELECT Sname, 2026 - Sage AS birth_year FROM Student;
SELECT DISTINCT Sdept FROM Student; -- 去掉重复行
-- ②选择:条件表达式
SELECT Sname FROM Student WHERE Sdept = 'CS' AND Sage < 20;
SELECT Sname FROM Student WHERE Sage BETWEEN 20 AND 23;
SELECT Sname FROM Student WHERE Sdept IN ('CS','MA');
SELECT Sname FROM Student WHERE Sname LIKE '刘%'; -- % 任意长度,_ 单个字符
SELECT Sno FROM SC WHERE Grade IS NULL; -- 空值判断不能用 = NULL
-- ③排序
SELECT * FROM Student ORDER BY Sdept ASC, Sage DESC;
-- ④统计:聚集函数忽略 NULL
SELECT COUNT(*), COUNT(DISTINCT Sno), AVG(Grade), MAX(Grade) FROM SC;
SELECT Cno, COUNT(Sno) AS cnt, AVG(Grade) AS avg_g
FROM SC GROUP BY Cno HAVING AVG(Grade) >= 80;
- SELECT 中出现的所有非聚集列都必须出现在 GROUP BY 中。
- COUNT(*) 统计行数(不忽略空行),COUNT(列) 跳过该列为空的行。
- LIKE 的
%与_含义不同;查询含通配符的字面量需 ESCAPE 转义。
课堂练习(3 题)
- 查询名字为两个汉字且姓"欧阳"的学生。
答:
SELECT Sname FROM Student WHERE Sname LIKE '欧阳__';(两个下划线各代表一个字符)。 - 统计各系人数,只显示人数大于 30 的系。
答:
SELECT Sdept, COUNT(*) FROM Student GROUP BY Sdept HAVING COUNT(*) > 30; - 为什么
WHERE Grade = NULL查不到任何行? 答:与 NULL 的比较结果是 UNKNOWN,不是 TRUE,行不会被选中;必须写Grade IS NULL。
第 4 课 连接查询
对应教材 3.3.2。把多张表按关联条件拼起来。
当所需信息分散在多个表中时,需要连接。内连接只保留能匹配上的行; 外连接保留一侧的全部行(未匹配的列填 NULL); 同一张表与自己连接叫自身连接,必须起别名才能区分两个实例。
-- 等值连接:两种写法等价 SELECT Student.Sno, Sname, Cno, Grade FROM Student, SC WHERE Student.Sno = SC.Sno; SELECT Student.Sno, Sname, Cname, Grade FROM Student JOIN SC ON Student.Sno = SC.Sno JOIN Course ON SC.Cno = Course.Cno; -- 自身连接:查询每门课的间接先修课 SELECT FIRST.Cno, SECOND.Cpno FROM Course FIRST, Course SECOND WHERE FIRST.Cpno = SECOND.Cno; -- 左外连接:保留未选课的学生 SELECT Student.Sno, Sname, Cno, Grade FROM Student LEFT OUTER JOIN SC ON Student.Sno = SC.Sno;
- 漏写连接条件会得到笛卡尔积(行数 = 两表行数之积),是典型错误。
- LEFT JOIN 保留左表全部行,RIGHT JOIN 保留右表,FULL OUTER JOIN 两侧都保留。
- 多表连接时列名冲突必须加表名前缀限定。
课堂练习(3 题)
- 查询每个学生及其选课情况,包括一门课都没选的学生。
答:用左外连接
FROM Student LEFT OUTER JOIN SC ON Student.Sno = SC.Sno。 - 查询选修 2 号课程且成绩在 90 分以上的学生姓名。
答:
SELECT Sname FROM Student JOIN SC ON Student.Sno = SC.Sno WHERE Cno = '2' AND Grade > 90; - 为什么自身连接必须给表起别名? 答:同一张表出现两次,不取别名无法区分引用的是哪一个实例,列名也会产生二义性。
第 5 课 嵌套查询与集合查询
对应教材 3.3.3 与 3.3.4。用子查询表达"在某个集合里 / 对全部都成立"。
当一个查询的条件依赖另一个查询的结果时,就把后者写成子查询。 不相关子查询可以独立执行(先算内层,再算外层);相关子查询依赖外层当前行(如 EXISTS)。 表达"全部 / 所有"这类全称量词时,标准写法是双层 NOT EXISTS。
-- IN:先算内层集合,再判断外层是否属于它
SELECT Sname FROM Student
WHERE Sno IN (SELECT Sno FROM SC WHERE Cno = '2');
-- ANY / ALL:与某个值 / 与全部值比较
SELECT Sname, Sage FROM Student
WHERE Sage < ANY (SELECT Sage FROM Student WHERE Sdept = 'CS'); -- 比其中某个小
SELECT Sname FROM Student
WHERE Sage > ALL (SELECT Sage FROM Student WHERE Sdept = 'CS'); -- 比全部都大
-- EXISTS:相关子查询,只关心"有没有返回行"
SELECT Sname FROM Student
WHERE EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = '1');
-- 全称量词:查询选修了全部课程的学生
SELECT Sname FROM Student
WHERE NOT EXISTS (
SELECT * FROM Course
WHERE NOT EXISTS (
SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = Course.Cno));
-- 语义:不存在这样一门课——该学生没有选它
-- 集合查询(列数相同、类型相容才能运算)
SELECT * FROM Student WHERE Sdept = 'CS'
UNION SELECT * FROM Student WHERE Sage <= 19; -- 并,UNION ALL 保留重复
SELECT Sno FROM SC WHERE Cno = '1'
INTERSECT SELECT Sno FROM SC WHERE Cno = '2'; -- 交
SELECT Sno FROM Student EXCEPT SELECT Sno FROM SC; -- 差
= ANY等价于 IN;<> ALL等价于 NOT IN。- EXISTS 子查询的 SELECT 列表习惯写
*,因为只看是否有行返回。 - 双层 NOT EXISTS 是"全部"类题目的通用模板,务必背下来。
课堂练习(3 题)
- 查询没有选修 1 号课程的学生姓名。
答:
SELECT Sname FROM Student WHERE NOT EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = '1'); - 查询与"刘晨"在同一个系学习的学生。
答:
SELECT Sname FROM Student WHERE Sdept IN (SELECT Sdept FROM Student WHERE Sname = '刘晨'); - UNION 与 UNION ALL 有何区别? 答:UNION 去掉重复行(需额外去重开销),UNION ALL 保留全部行(包括重复),效率更高。
第 6 课 数据更新与空值
对应教材 3.4 与 3.5。
插入、修改、删除都会触发完整性检查:违反主码、外码或 CHECK 时语句会被拒绝。 更新操作可以带子查询,用来描述"对满足某条件的一批行进行操作"。
-- 插入:单条与批量(子查询结果)
INSERT INTO Student (Sno, Sname, Ssex, Sdept, Sage) VALUES ('201215128','陈冬','男','IS',18);
INSERT INTO Deptage(Sdept, Avgage)
SELECT Sdept, AVG(Sage) FROM Student GROUP BY Sdept;
-- 修改:全部、按条件、带子查询
UPDATE Student SET Sage = Sage + 1;
UPDATE Student SET Sage = 22 WHERE Sno = '201215121';
UPDATE SC SET Grade = 0
WHERE 'CS' = (SELECT Sdept FROM Student WHERE Student.Sno = SC.Sno);
-- 删除
DELETE FROM SC WHERE Sno = '201215128';
DELETE FROM SC
WHERE 'CS' = (SELECT Sdept FROM Student WHERE Student.Sno = SC.Sno);
DELETE FROM SC; -- 清空数据,保留表结构
-- 空值
SELECT Sno, Cno FROM SC WHERE Grade IS NULL;
SELECT Sno FROM SC WHERE Grade IS NOT NULL AND Grade < 60;
- 删除被参照表的行时若有外码引用会被拒绝,除非定义了级联删除——先删子表再删父表。
- 空值参与算术运算结果为 NULL,参与比较得到 UNKNOWN(三值逻辑)。
课堂练习(2 题)
- 把计算机系全体学生的成绩置零,写出语句。
答:
UPDATE SC SET Grade = 0 WHERE 'CS' = (SELECT Sdept FROM Student WHERE Student.Sno = SC.Sno); - 删除"刘晨"同学时先做什么?为什么? 答:先删除 SC 中该同学的选课记录,再删 Student 中的记录;否则违反参照完整性(除非外码定义了 ON DELETE CASCADE)。
第 7 课 视图
对应教材 3.6。虚表:只存定义,不存数据。
视图是从一张或多张基本表导出的虚表,数据库中只保存它的定义。 查询视图时,DBMS 会做视图消解:把对视图的查询转换成对基本表的等价查询。 更新视图时,DBMS 需要把更新唯一地映射回基本表——因此由聚集、分组、DISTINCT 导出的视图通常不可更新。
CREATE VIEW CS_Student AS SELECT Sno, Sname, Sage FROM Student WHERE Sdept = 'CS' WITH CHECK OPTION; -- 通过视图更新时仍受视图条件约束 CREATE VIEW S_G(Sno, Gavg) AS -- 分组视图:通常不可更新 SELECT Sno, AVG(Grade) FROM SC GROUP BY Sno; SELECT * FROM CS_Student WHERE Sage < 20; -- 视图消解为对 Student 的查询 UPDATE CS_Student SET Sname = '刘辰' WHERE Sno = '201215121'; DROP VIEW CS_Student;
- 视图的作用:简化操作、多角度看待数据、提供逻辑独立性、安全保护、清晰表达查询。
- 行列子集视图可更新;含聚集 / GROUP BY / DISTINCT 的视图不可更新。
- WITH CHECK OPTION 保证通过视图插入或修改的行仍满足视图定义条件。
课堂练习(2 题)
- 为什么对 S_G(平均成绩视图)执行 UPDATE 会被拒绝? 答:该视图由 GROUP BY 与聚集函数导出,一行视图对应基本表中的多行,无法唯一映射回基本表。
- 为 CS_Student 加了 WITH CHECK OPTION 后,插入一个 Sdept 为 'MA' 的学生会怎样?
答:会被拒绝,因为插入结果不满足视图定义中的
Sdept = 'CS'条件。
第 8 课 授权与事务
对应教材第 4 章(安全)与第 11 章(事务)。
授权解决"谁能做什么":自主存取控制用 GRANT 授予、REVOKE 收回,可按列授权,也可用角色批量管理。 事务解决"一组操作要么全做要么全不做":以 COMMIT 提交或 ROLLBACK 回滚结束,具备 ACID 特性。
-- 授权与收回 GRANT SELECT ON TABLE Student TO U1 WITH GRANT OPTION; -- 允许 U1 再授权 GRANT UPDATE(Sage), SELECT ON TABLE Student TO U2, U3; -- 列级权限 REVOKE UPDATE(Sage) ON TABLE Student FROM U2; -- 角色:先授权给角色,再把角色授予用户 CREATE ROLE R1; GRANT SELECT, UPDATE ON TABLE Student TO R1; GRANT R1 TO 王平, 张明; -- 命名约束,便于后续修改 CREATE TABLE Student ( Sno NUMERIC(6) CONSTRAINT C1 CHECK (Sno BETWEEN 90000 AND 99999), CONSTRAINT StudentKey PRIMARY KEY (Sno) ); ALTER TABLE Student DROP CONSTRAINT C1; -- 事务 BEGIN TRANSACTION; UPDATE Account SET balance = balance - 100 WHERE id = 'A'; UPDATE Account SET balance = balance + 100 WHERE id = 'B'; COMMIT; -- 任一步出错则执行 ROLLBACK;
- WITH GRANT OPTION 表示被授权者可继续转授;收回时需写 CASCADE 才会级联收回。
- ACID:原子性、一致性、隔离性、持久性;事务是恢复与并发控制的基本单位。
课堂练习(2 题)
- U1 把权限转授给了 U2,回收 U1 的权限后 U2 是否仍有权限? 答:若 REVOKE 未写 CASCADE,U2 仍保留权限;写 CASCADE 才会一并收回。
- 转账过程中第二步更新失败,应如何处理? 答:执行 ROLLBACK 回滚事务,使数据库回到 BEGIN 之前的状态,保证原子性。
阶段自测
五道综合题,覆盖前八课的核心写法。先自己写,再展开对照。
查看题目与参考答案(点击折叠)
- 查询选修 2 号课程且成绩不低于 90 分的学生姓名与成绩。
答:
SELECT Sname, Grade FROM Student JOIN SC ON Student.Sno = SC.Sno WHERE Cno = '2' AND Grade >= 90; - 统计每门课程的选课人数与平均分,只显示平均分不低于 80 的课程。
答:
SELECT Cno, COUNT(Sno), AVG(Grade) FROM SC GROUP BY Cno HAVING AVG(Grade) >= 80; - 查询一门课都没有选修的学生姓名。
答:
SELECT Sname FROM Student WHERE NOT EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno); - 把计算机系(CS)全体学生的年龄加 1。
答:
UPDATE Student SET Sage = Sage + 1 WHERE Sdept = 'CS'; - 建立"计算机系学生"视图,并说明它是否可更新。
答:
CREATE VIEW CS_Student AS SELECT Sno, Sname, Sage FROM Student WHERE Sdept = 'CS';它属于行列子集视图,可以更新;若定义中含聚集函数或 GROUP BY 则不可更新。