第三章 关系型数据库的SQL语言
3.1 SQL概述
- SQL(Structured Query Language,结构化查询语言)是关系数据库的标准语言。
3.1.1 SQL的特点
- 综合统一 :
- 集数据定义语言(DDL)、数据操纵语言(DML)、数据控制语言(DCL)的功能于一体。
- 能够独立完成数据库生命周期中的几乎全部活动,包括:
- 定义、修改和删除关系模式、视图、索引等;
- 对数据库进行查询和更新;
- 数据库的重构与维护 ;
- 数据库安全性、完整性控制及事务控制;
- 支持嵌入式SQL和动态SQL。
- 高度非过程化 :
- 用户只需提出"做什么",而无需指明"怎么做" ;
- 数据的存取路径由数据库管理系统(DBMS)自动选择和优化,大大减轻了用户负担。
- 面向集合的操作方式 :
- 与非关系数据模型面向记录的操作方式不同,SQL的操作对象、处理结果都是元组的集合;
- 一次
INSERT
、UPDATE
、DELETE
操作可以作用于一批元组。
- 以同一种语法结构提供多种使用方式 :
- 独立语言: 可作为独立的语言,通过交互式终端直接使用(联机交互) 22。
- 嵌入式语言: 可嵌入到高级编程语言(如C/C++, Java)中,供程序员在应用程序中使用 23。
- 语言简洁,易学易用 :
- 虽然功能强大,但其核心功能仅由9个动词构成。
SQL功能 | 动词 |
---|---|
数据查询 | SELECT |
数据定义 | CREATE , DROP , ALTER |
数据操纵 | INSERT , UPDATE , DELETE |
数据控制 | GRANT , REVOKE |
3.1.2 SQL的基本概念
- 三级模式结构 : SQL支持关系数据库的三级模式结构:外模式、模式和内模式。
- 外模式 : 对应于视图(
VIEW
),是用户能看到和操作的数据视图。 - 模式 : 对应于基本表(
BASE TABLE
),是数据库中全体数据的逻辑结构描述。 - 内模式 : 对应于存储文件(
STORAGE FILE
),描述数据在物理存储介质上的组织方式。
- 外模式 : 对应于视图(

3.2 学生-课程数据库(示例)
- 学生表:
Student
(Sno
,Sname
,Ssex
,Sbirthdate
,Smajor
) - 课程表:
Course
(Cno
,Cname
,Ccredit
,Cpno
) - 选课表:
SC
(Sno
,Cno
,Grade
,Semester
,Teachingclass
)

3.3 数据定义 (DDL)
- SQL的数据定义功能用于定义数据库中的各种"对象",如模式、表、视图和索引。
操作对象 | 创建 (CREATE) | 删除 (DROP) | 修改 (ALTER) |
---|---|---|---|
模式 (SCHEMA) | CREATE SCHEMA |
DROP SCHEMA |
|
表 (TABLE) | CREATE TABLE |
DROP TABLE |
ALTER TABLE |
视图 (VIEW) | CREATE VIEW |
DROP VIEW |
|
索引 (INDEX) | CREATE INDEX |
DROP INDEX |
ALTER INDEX |
- 数据字典 : 是DBMS内部的一组系统表,记录了所有数据库对象的定义信息(元数据)。当执行DDL语句时,DBMS实际上是在更新数据字典中的信息。
- 关系模式、表、视图、索引的定义;
- 完整性约束的定义;
- 各类用户对数据库的操作权限;
- 统计信息等。
3.3.1 模式
1.定义模式 CREATE SCHEMA
:
sql
CREATE SCHEMA [<模式名>] AUTHORIZATION <用户名>;
-
功能: 为指定用户定义一个模式,定义模式实际上定义了一个命名空间,在这个空间中可以进一步定义该模式包含的数据库对象,例如基本表、视图、索引等等。
-
说明 : 如果不指定
<模式名>
,则模式名默认为<用户名>
。 -
权限 : 执行此操作需要DBA权限或 被授予
CREATE SCHEMA
的权限。sql-- 为用户WANG定义一个名为S-C-SC的模式 CREATE SCHEMA "S-C-SC" AUTHORIZATION WANG;
-
CREATE SCHEMA
语句中还可以包含CREATE TABLE
,CREATE VIEW
和GRANT
子句,用于在创建模式的同时定义其中的对象。sql-- 为用户ZHANG创建了一个模式TEST,并且在其中定义一个表TAB1 -- CREATE SCHEMA TEST AUTHORIZATION ZHANGCREATE CREATE TABLE TAB1 (COL1 SMALLINT, COL2 INT, COL3 CHAR(20), COL4 NUMERIC(10,3), COL5 DECIMAL(5,2)
2.删除模式 DROP SCHEMA
:
sql
DROP SCHEMA <模式名> <CASCADE | RESTRICT>;
-
CASCADE
(级联): 删除模式的同时,其中的所有数据库对象(表、视图等)也一并被删除。 -
RESTRICT
(限制): 只有当模式中不包含任何数据库对象时才能删除,否则拒绝执行。sql-- 级联删除名为TEST的模式 DROP SCHEMA TEST CASCADE;
3.3.2 表 (Table)
1.定义基本表 CREATE TABLE
:
sql
CREATE TABLE <表名>
(<列名> <数据类型> [<列级完整性约束条件>],
...
[<表级完整性约束条件>]
);
-
列级约束: 只涉及单个列的约束。
-
表级约束: 涉及一个或多个列的约束。如果一个约束涉及多个列,必须定义在表级。
-
示例:
sql-- 创建Student表 CREATE TABLE Student ( Sno CHAR(8) PRIMARY KEY, -- Sno是主码,列级约束 Sname VARCHAR(20) UNIQUE, -- Sname取值唯一,列级约束 Ssex CHAR(6), Sbirthdate DATE, Smajor VARCHAR(40) ); -- 创建Course表 CREATE TABLE Course ( Cno CHAR(5) PRIMARY KEY, Cname VARCHAR(40) NOT NULL, Ccredit SMALLINT, Cpno CHAR(5), -- 先修课 -- Cpno是外码,参照Course表自身的Cno,表级约束 FOREIGN KEY (Cpno) REFERENCES Course(Cno) ); -- 创建SC表 CREATE TABLE SC ( Sno CHAR(8), Cno CHAR(5), Grade SMALLINT, Semester CHAR(5), Teachingclass CHAR(8), PRIMARY KEY (Sno, Cno), -- 主码由两个属性构成,必须定义为表级约束 FOREIGN KEY (Sno) REFERENCES Student(Sno), -- Sno是外码,参照Student表 FOREIGN KEY (Cno) REFERENCES Course(Cno) -- Cno是外码,参照Course表 );
常见数据类型:
数据类型 | 含义 |
---|---|
CHAR(n) |
长度为n的定长字符串 |
VARCHAR(n) |
最大长度为n的变长字符串 |
INT , INTEGER |
整型(通常4字节) |
SMALLINT |
短整型(通常2字节) |
NUMERIC(p, d) |
定点数,共p位,小数d位 |
REAL , DOUBLE |
浮点数 |
DATE |
日期 (YYYY-MM-DD) |
TIME |
时间 (HH:MM:SS) |
TIMESTAMP |
时间戳 |
模式与表
-
每一个基本表属于某一个模式,一个模式包含多个基本表。
-
在表名中显式地给出模式名;
-
在创建模式语句中同时创建表;
-
设置所属的模式,这样在创建表时表名中不必给出模式名。
2.修改基本表 ALTER TABLE
:
sql
ALTER TABLE <表名>
[ ADD [COLUMN] <新列名> <数据类型> [完整性约束] ]
[ DROP [COLUMN] <列名> [CASCADE | RESTRICT] ]
[ ALTER COLUMN <列名> TYPE <数据类型> ]
[ ADD <表级完整性约束> ]
[ DROP CONSTRAINT <完整性约束名> [CASCADE | RESTRICT] ];
-
ADD
: 增加新列或新的完整性约束。 -
DROP
: 删除列或完整性约束。CASCADE
会删除引用了该列的其他对象,RESTRICT
则在有依赖时拒绝删除。 -
DROP CONSTRAINT
: 删除指定的完整性约束条件。 -
ALTER COLUMN
: 修改列的数据类型或名称。 -
示例:
sql-- 向Student表增加"邮箱地址"列 ALTER TABLE Student ADD Semail VARCHAR(30); -- 增加课程名称必须取唯一值的约束 ALTER TABLE Course ADD UNIQUE(Cname);
3.删除基本表 DROP TABLE
:
sql
DROP TABLE <表名> [RESTRICT | CASCADE];
-
RESTRICT
: 如果存在依赖该表的对象(如视图、外键约束),则删除操作受限,不能执行。 -
CASCADE
: 删除该表的同时,所有相关的依赖对象(如视图、索引等)也一并删除。 -
示例:
sql-- 级联删除Student表 DROP TABLE Student CASCADE;
3.3.3 索引 (Index)
- 建立索引
CREATE INDEX
:
sql
CREATE [UNIQUE] [CLUSTER] INDEX <索引名>
ON <表名> (<列名> [ASC|DESC], ...);
-
索引可以建立在该表的一列 或者多列上。
-
UNIQUE
: 唯一索引,要求索引列的值唯一,相当于增加了一个UNIQUE
约束。 -
CLUSTER
: 聚簇索引,表中的数据将按照索引列的顺序进行物理存储。一个表最多只能有一个聚簇索引。
sql
-- 在Student表的Sname(姓名)列上建立一个聚索引,而且Student表中的记录将按照Sname值的升序存放 --
CREATE CLUSTER INDEX Stusname ON Student(Sname);
-
ASC
/DESC
: 指定索引的排序方式,默认为升序ASC
。 -
示例:
sql-- 为学生-课程数据库中的Student,Course,Sc三个表建立索引。Student表按姓名升序建唯一索引,Course表按课程名升序建唯一索引,SC表按学号升序和课程号降序建唯一索引 CREATE UNIQUE INDEX Idx_StuSname ON Student(Sname); CREATE UNIQUE INDEX ldx CouCname ON Course(cname); CREATE UNIQUE INDEX Idx_SCCno ON SC(Sno ASC, Cno DESC);
-
修改索引
ALTER INDEX
:- 主要用于重命名。
sql
ALTER INDEX <旧索引名> RENAME TO <新索引名>;
sql
ALTER INDEX Idx_SCCno RENAME TO Idx_SCSnoCno;
-
删除索引
DROP INDEX
:sqlDROP INDEX <索引名>;
-
示例:
sqlDROP INDEX Idx_StuSname;
-
3.4 数据查询 (DQL)
3.4.1 SELECT
语句概述
-
通用语法结构:
sqlSELECT [ALL | DISTINCT] <目标列表达式>[,<目标列表达式>]... -- 指定要显示的属性列 -- FROM <表名或视图名>[,<表名或视图名>]...| (SELECT 语句) [AS]<别名> -- 指定查询对象 -- [WHERE <条件表达式>] -- 指定查询条件 -- [GROUP BY <列名> [HAVING <条件表达式>]] [ORDER BY <列名> [ASC | DESC]]; -- 排序 --
-
子句功能:
-
SELECT
: 指定要查询和显示的列。 -
FROM
: 指定查询的数据来源,可以是表或视图。 -
WHERE
: 设置查询条件,过滤元组。 -
GROUP BY
: 将结果按指定列的值进行分组,值相等的元组为一组,常与聚合函数一起使用。 -
HAVING
: 对GROUP BY
分组后的结果进行过滤,筛选出满足条件的组。 -
ORDER BY
: 对最终查询结果进行排序。
-
3.4.2 单表查询
-
选择表中的列:
-
查询指定列 :
SELECT Sno, Sname FROM Student;
-
查询全部列 :
SELECT * FROM Student;
-
查询计算值(虚列) : 可以在
SELECT
子句中使用表达式、常量和列别名。
-
sql
-- 查询学生姓名和年龄
SELECT Sname, (extract(year from current_date) - extract(year from Sbirthdate)) AS "年龄"
FROM Student;
-- 使用列别名
SELECT Sname AS NAME, Smajor AS MAJOR FROM Student;
-
选择表中的元组:
-
消除重复行
DISTINCT
:SELECT
默认为ALL
,即保留所有行。使用DISTINCT
可以去除结果中的重复行。sql-- 查询所有选修了课程的学生学号(去重) SELECT DISTINCT Sno FROM SC;
-
使用
WHERE
子句查询满足条件的元组:
-
查询条件类型 | 谓词 | 示例 |
---|---|---|
比较大小 | = , > , < , >= , <= , != |
WHERE Grade < 60; 64 |
确定范围 | (NOT) BETWEEN ... AND ... |
WHERE Age BETWEEN 20 AND 23; 65 |
确定集合 | (NOT) IN (...) |
WHERE Smajor IN ('计算机科学与技术', '信息安全'); 66 |
字符匹配 | LIKE , NOT LIKE |
WHERE Sname LIKE '刘%'; 67 |
空值判断 | IS NULL , IS NOT NULL |
WHERE Grade IS NULL; 68 |
多重条件 | AND , OR , NOT |
WHERE Smajor='...' AND Age > 20; 69 |
字符匹配 中,
%
代表任意长度(包括0)的字符串,_
代表任意单个字符,ESCAPE
用于定义换码字符,以匹配通配符本身。
sql
-- 1.查询主修计算机科学与技术专业的学生姓名
SELECT Sname FROM Student WHERE Smajor = '计算机科学与技术';
-- 2.比较大小,查询所有2000年后(包括2000年)出生的学生姓名及其性别
SELECT Sname,Sex
FROM Student
WHERE extract(year from Sbirthdate)>=2000;/*函数extract(year from Sbirthdate)从出生日期中抽取出年份*/
-- 3.确定范围,查询年龄不在20到23岁之间的学生姓名、出生日期、主修专业
SELECT Sname,Sbirthdate,Smajor FROM Student
WHERE extract(year from current_date) - extract(year from Sbirthdate) NOT BETWEEN 20 AND 23;
-- 4.确定集合,查询计算机科学与技术和信息安全专业的学生
SELECT Sname, Ssex FROM Student
WHERE Smajor IN ('计算机科学与技术', '信息安全');
sql
-- 5.1字符匹配,查询所有姓刘的学生
SELECT Sname, Sno FROM Student WHERE Sname LIKE '刘%';
-- 5.2字符匹配,查询课程名包含 'DB_Design' 的课程,这里_是一个普通字符,需要用换码符\转义
SELECT Cno, Ccredit FROM Course WHERE Cname LIKE 'DB\_Design' ESCAPE '\';
-- 5.3字符匹配,查询以DB_开头,且倒数第3个字符为i的课程的详细情况'
SELECT *
FROM Course
WEHERE Cname LIKE 'DB\_%i__' ESCAPE '\' ;
sql
-- 6.涉及空值的查询,查询缺少成绩的选课记录
SELECT Sno, Cno FROM SC WHERE Grade IS NULL;/*注意:"IS"不能用"="代替*/
sql
-- 7.多重条件查询,AND优先级高于OR,并且可以用括号改变优先级
-- 查询计算机专业且出生于2000年及之后的学生
SELECT Sno, Sname FROM Student
WHERE Smajor = '计算机科学与技术' AND extract(year from Sbirthdate) >= 2000;
-
对查询结果排序
ORDER BY
ASC
: 升序 (默认);DESC
: 降序。- 可以按多列进行排序。
sql-- 查询选修了 81003 号课程的学生的学号和成绩,按分数降序排列 SELECT Sno, Grade FROM SC WHERE Cno = '81003' ORDER BY Grade DESC; -- 查询所有选课记录,先按课程号升序,再按成绩降序 SELECT * FROM SC ORDER BY Cno ASC, Grade DESC;
-
使用聚合函数,聚合函数对一组值进行计算,并返回单个值。
函数 | 描述 |
---|---|
COUNT(*) |
统计元组个数 |
COUNT([DISTINCT] col) |
统计一列中值的个数 |
SUM([DISTINCT] col) |
计算一列值的总和(数值型) |
AVG([DISTINCT] col) |
计算一列值的平均值(数值型) |
MAX(col) / MIN(col) |
求一列中的最大/最小值 |
sql
-- 查询选修81001号课程的学生最高分数
SELECT MAX(Grade)
FROM SC
WHERE Cno='81001';
-- 查询以81002为先修课的课程总学分数
SELECT SUM(Ccredit)
FROM Course
WHERE Cpno='81002';
- 对查询结果分组
GROUP BY
GROUP BY
子句将查询结果按一列或多列的值进行分组,值相等的为一组。聚合函数会分别作用于每个分组。
sql
-- 求每个课程的课程号及相应的选课人数
SELECT Cno, COUNT(Sno)
FROM SC
GROUP BY Cno;
-
使用
HAVING
过滤分组 :HAVING
子句用于筛选满足指定条件的分组。sql-- 查询2019年第2学期选修了10门以上课程的学生学号 SELECT Sno FROM SC WHERE Semester='20192' GROUP BY Sno HAVING COUNT(*) > 10;
WHERE
与HAVING
的区别
作用对象不同 :
WHERE
作用于基表或视图的元组(行),在分组前进行过滤。HAVING
作用于分组,在分组后进行过滤。条件不同 :
WHERE
子句中不能使用聚合函数。HAVING
子句中通常包含聚合函数。
sql
-- 查询平均成绩大于等于90分的学生学号和平均成绩
SELECT Sno, AVG(Grade)
FROM SC
GROUP BY Sno
HAVING AVG(Grade) >= 90;
单表查询综合练习
sql
-- 练习1 列出计算科学与技术专业姓刘的同学的信息,按照学号大小排序
SELECT *
FROM Student
WHERE Smajor='计算机科学与技术'
AND Sname LIKE '刘%'
ORDER BY Sno;
sql
-- 练习2 按专业并区分男女统计各专业学生的人数,并按照人数降序排序
SELECT Smajor,Sex,COUNT(Sno)
FROM Student
GROUP BY Smajor,Sex
ORDER BY COUNT(Sno) DESC;
3.4.3 连接查询
-
当查询涉及多个表 时,就需要使用连接查询。在SQL中,连接是通过在
WHERE
子句中指定"连接条件"或"连接谓词"来实现的。sqlSELECT [ALL|DISTINCT] <目标列>[,<目标列>]... FROM <表名或视图名>[,<表名或视图名>]...|(SELECT语句) [AS]<别名> [WHERE <条件表达式>] [GROUP BY <列名1>[HAVING <条件表达式>]] [ORDER BY <列名2>[ASC|DESC]] [LIMIT <行数1> [OFFSET <行数2>]];
sqlSELECT S.Sname, SC.Grade FROM Student S, Course C, SC WHERE S.Sno = SC.Sno AND C.Cno = SC.Cno AND C.Cname = '数据库系统概论' AND SC.Grade > 85 ORDER BY SC.Grade DESC;
1. 等值与非等值连接查询
-
等值连接 : 连接运算符为
=
的连接查询。-
示例: 查询每个学生及其选修课程的情况。
sqlSELECT Student.*, SC.* FROM Student, SC WHERE Student.Sno = SC.Sno;
-
-
自然连接 : 是一种特殊的等值连接,它要求两个关系中进行比较的分量必须是相同的属性组,并且在结果中把重复的属性列去掉。SQL中没有直接的自然连接操作符,但可以通过在
SELECT
子句中指定要查询的列并去除重复列来模拟。-
示例: 对上一个例子用自然连接的方式完成。
sqlSELECT Student.Sno, Sname, Ssex, Sbirthdate, Smajor, Cno, Grade, Semester, Teachingclass FROM Student, SC WHERE Student.Sno = SC.Sno;
-
-
复合条件查询 : 一条SQL语句可以同时包含连接谓词和选择谓词,从而同时完成连接和选择操作。
-
示例: 查询选修81002号课程且成绩在90分以上的所有学生的学号和姓名。
sqlSELECT Student.Sno, Sname FROM Student, SC WHERE Student.Sno = SC.Sno -- 连接谓词 AND SC.Cno = '81002' -- 选择谓词 AND SC.Grade > 90; -- 选择谓词
-
2. 自身连接
-
一个表与其自己进行的连接称为自身连接。
-
进行自身连接时,必须为表指定别名以作区分,并且所有属性引用都必须使用别名前缀。
-
示例: 查询每一门课的间接先修课(即先修课的先修课)。
sqlSELECT FIRST.Cno, SECOND.Cpno FROM Course FIRST, Course SECOND WHERE FIRST.Cpno = SECOND.Cno AND SECOND.Cpno IS NOT NULL;
这里,
FIRST
和SECOND
都是Course
表的别名。查询的逻辑是找到一门课FIRST
,其先修课号FIRST.Cpno
等于另一门课SECOND
的课程号SECOND.Cno
,然后输出FIRST
的课程号和SECOND
的先修课号SECOND.Cpno
。
3. 外连接
-
与普通连接的区别:
- 普通连接(内连接)只输出满足连接条件的元组。
- 外连接会保留主体表中不满足连接条件的元组,并在另一表的对应列上填上
NULL
值。
-
类型:
- 左外连接 (
LEFT OUTER JOIN
): 列出左边关系(主表)中的所有元组。 - 右外连接 (
RIGHT OUTER JOIN
): 列出右边关系(主表)中的所有元组。
- 左外连接 (
-
示例 : 以
Student
表为主体,列出每个学生的基本情况及其选课情况。若学生未选课,其选课信息(课程号、成绩)填NULL
。sqlSELECT Student.Sno, Sname, Ssex, Sbirthdate, Smajor, Cno, Grade FROM Student LEFT OUTER JOIN SC ON (Student.Sno = SC.Sno);
这个查询会返回
Student
表中的所有学生,包括那些在SC
表中没有选课记录的学生(如"赵明"和"王佳佳"),这些学生的Cno
和Grade
列将显示为NULL
。
4. 多表连接
-
涉及两个以上表的连接。
-
示例 : 查询每个学生的学号、姓名、选修的课程名及成绩。这需要连接
Student
,SC
, 和Course
三个表。sqlSELECT Student.Sno, Sname, Cname, Grade FROM Student, SC, Course WHERE Student.Sno = SC.Sno AND SC.Cno = Course.Cno;
3.4.4 嵌套查询
-
一个
SELECT-FROM-WHERE
语句称为一个查询块 。将一个查询块嵌套在另一个查询块的WHERE
子句或HAVING
短语中,就构成了嵌套查询。 -
父查询/外层查询: 上层的查询块。
-
子查询/内层查询: 下层的查询块。
-
限制 : 子查询中不能使用
ORDER BY
子句。 -
分类:
- 不相关子查询: 子查询的执行不依赖于父查询。执行时由里向外,子查询的结果作为父查询的条件。
- 相关子查询: 子查询的执行依赖于父查询的当前元组。执行时,父查询每取出一个元组,就代入子查询中执行一次。
1. 带有IN
谓词的子查询
-
IN
谓词用于判断父查询的某个属性值是否在子查询返回的结果集合中。 -
示例: 查询与"刘晨"在同一个主修专业的学生。
sql-- 不相关子查询 SELECT Sno, Sname, Smajor FROM Student WHERE Smajor IN (SELECT Smajor FROM Student WHERE Sname = '刘晨'); -- 自身连接算法 SELECT S1.Sno,S1.Sname,S1.major FROM Student S1,Student S2 WHERE S1.Smajor=S2.Smajor AND S2.Sname='刘晨';

2. 带有比较运算符的子查询
-
当能确定子查询返回的是单个值时,可以使用比较运算符(
=
,>
,<
,!=
等)。 -
示例: 查询与"刘晨"在同一个主修专业的学生的学号、姓名和主修专业(假设姓名唯一,则专业也唯一)。
sqlSELECT Sno, Sname, Smajor FROM Student WHERE Smajor = (SELECT Smajor FROM Student WHERE Sname = '刘晨');
-
示例 (相关子查询): 找出每个学生超过他自己选修课程平均成绩的课程号。
sqlSELECT Sno, Cno FROM SC x -- x是SC表的别名 WHERE Grade >= (SELECT AVG(Grade) FROM SC y -- y是SC表的别名 WHERE y.Sno = x.Sno); -- 子查询依赖于父查询的x.Sno
3. 带有ANY
(或SOME
)或ALL
谓词的子查询
-
ANY
/SOME
和ALL
必须与比较运算符一起使用。> ANY
: 大于子查询结果中的某一个值(即大于最小值)任意一个 。> ALL
: 大于子查询结果中的所有值(即大于最大值)所有。!=(或<>) ANY
: 不等于子查询结果中的某个值。!=(或<>) ALL
: 不等于子查询结果中的任何一个值。- 其他运算符以此类推。
-
= ANY
等价于IN
。 -
!= ALL
等价于NOT IN
。 -
示例1 : 查询非计算机专业中,比计算机专业任意一个学生年龄小的学生姓名、出生日期和主修专业(即出生日期晚于计算机专业最早的出生日期)。
sqlSELECT Sname, Sbirthdate, Smajor FROM Student WHERE Sbirthdate > ANY (SELECT Sbirthdate FROM Student WHERE Smajor = '计算机科学与技术') AND Smajor <> '计算机科学与技术';
这等价于使用聚集函数
MIN
:sqlSELECT Sname, Sbirthdate, Smajor FROM Student WHERE Sbirthdate > (SELECT MIN(Sbirthdate) FROM Student WHERE Smajor = '计算机科学与技术') AND Smajor <> '计算机科学与技术';
-
示例2 :查询非计算机科学技术专业中比计算机科学技术专业所有学生年龄小(出生日期晚)的学生姓名、出生日期和主修专业。
SQLSELECT Sname,Sbirthdate,Smajor FROM Student WHERE Sbirthday > ALL (SELECT Sbirthdate FROM Student WHERE Smajor='计算机科学与技术') AND Smajor <> '计算机科学与技术';
这等价于使用聚集函数
MAX
:SQLSELECT Sname, Sbirthdate, Smajor FROM Student WHERE Sbirthdate > (SELECT MAX(Sbirthdate) FROM Student WHERE Smajor = '计算机科学与技术') AND Smajor <> '计算机科学与技术';
-
ANY
(或SOME
),ALL
谓词与聚集函数、IN
谓词的等价转换关系。=
<>
或!=
<
<=
>
>=
ANY IN < MAX <= MAX > MIN >= MIN ALL NOT IN < MIN <=MIN > MAX >= MAX
4. 带有EXISTS
谓词的子查询
-
EXISTS
谓词是存在量词(∃),它不返回任何数据,只产生逻辑真值true
或false
。- 如果子查询结果集非空,
EXISTS
返回true
。 - 如果子查询结果集为空,
EXISTS
返回false
。
- 如果子查询结果集非空,
-
NOT EXISTS
则相反。 -
EXISTS
子查询的目标列表达式通常用*
,因为列名无实际意义。 -
EXISTS
通常用于相关子查询。 -
示例: 查询所有选修了81001号课程的学生姓名。
sqlSELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = '81001');
不同形式的查询间的替换
-
一些带
EXISTS
或NOT EXISTS
谓词的子查询不能被其他形式的子查询等价替换; -
所有带
IN
谓词、比较运算符、ANY
和ALL
谓词的子查询都能用带EXISTS
谓词的子查询等价替换。SQLSELECT Sno,Sname,Smajor FROM Student S1 WHERE EXISTS (SELECT * FROM Student S2 WHERE S2.Smajor=S1.Smajor AND S2.$name='刘晨'); -- 等价于 -- SELECT Sno, Sname, Smajor FROM Student WHERE Smajor IN (SELECT Smajor FROM Student WHERE Sname='刘晨');
用EXISTS
实现全称量词(∀) (难点)
-
SQL没有全称量词,但可以利用逻辑等价转换:
(∀x)P ≡ ¬(∃x(¬P))
。 -
示例: 查询选修了全部课程的学生姓名。
- 思路: 转换为=="不存在一门课程是他没有选修的"==。
SQLSELECT Sname FROM Student WHERE NOT EXISTS -- 不存在 (SELECT * FROM Course WHERE NOT EXISTS -- 一门他没有选的课 (SELECT * FROM SC WHERE Sno = Student.Sno AND Cno = Course.Cno));
用EXISTS
实现逻辑蕴涵(p → q) (难点)
-
SQL没有逻辑蕴涵,但可以利用等价转换:
p → q ≡ ¬p ∨ q
。 -
示例: 查询至少选修了学生'20180002'所选全部课程的学生号码。
- 思路: 转换为"不存在一门课程,'20180002'选了,而该学生没选" 。
sqlSELECT Sno FROM Student S1 -- 使用S1代替Student以避免歧义 WHERE NOT EXISTS (SELECT * FROM SC SC1 WHERE SC1.Sno = '20180002' AND -- 课程'20180002'选了 NOT EXISTS (SELECT * FROM SC SC2 WHERE SC2.Sno = S1.Sno AND -- 而S1没选 SC2.Cno = SC1.Cno));
3.4.5 集合查询
-
操作符:
UNION
(并集): 合并结果集,并自动去除重复元组,UNION ALL
则保留重复元组。INTERSECT
(交集): 返回两个结果集的交集。EXCEPT
(差集): 返回在第一个结果集中存在,但在第二个结果集中不存在的元组。
-
要求: 参与运算的各个查询结果的列数必须相同,且对应列的数据类型也必须兼容。
-
示例 (UNION): 查询计算机科学专业的学生,以及年龄不大于19岁的学生。
sqlSELECT * FROM Student WHERE Smajor = '计算机科学与技术' UNION SELECT * FROM Student WHERE (extract(year from current_date) - extract(year from Sbirthdate)) <= 19;
-
对集合操作结果排序 :
ORDER BY
子句只能用在最后一个查询之后,对最终结果进行排序。对集合操作结果排序时,ORDER BY
子句中用数字i
指定排序属性,即结果的第i
列。
3.4.6 基于派生表的查询
-
当子查询出现在
FROM
子句中时,该子查询的结果会形成一个临时的"派生表",成为主查询的操作对象。 -
必须为派生表指定一个别名。
-
示例1: 找出每个学生超过他自己选修课程平均成绩的课程号。
sqlSELECT SC.Sno, SC.Cno FROM SC, (SELECT Sno, AVG(Grade) AS Avg_grade FROM SC GROUP BY Sno) AS Avg_SC (Avg_sno, Avg_grade_col) -- 派生表Avg_SC及其列别名 WHERE SC.Sno = Avg_SC.Avg_sno AND SC.Grade >= Avg_SC.Avg_grade_col;
-
示例2: 查询所有选修了81001号课程的学生姓名。
sql-- 方法1 -- SELECT Sname FROM Student, (SELECT SNO FROM SC WHERE Cno='81001 ')AS SC1 WHERE Student.Sno=sc1.Sno; -- 方法2 -- SELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno=Student.Sno AND Cno='81001'); -- 方法3 -- SELECT Sname FROM Student,SC WHERE Student.Sno=SC.Sno AND Cno='81001'; -- 方法4 -- SELECT Sname FROM Student WHERE Sno IN (SELECT distinct Sno FROM SC WHERE Cno='81001');
3.5 数据更新 (DML)
3.5.1 插入数据 INSERT
1.插入元组
sql
INSERT INTO <表名> [(<列1>, <列2>, ...)]
VALUES (<值1>, <值2>, ...);
- 如果未指定列名,则
VALUES
子句必须为所有列提供值,且顺序与表定义一致。 - 如果指定了部分列名,则未指定的列将被赋
NULL
值(前提是该列允许为NULL
)。
2.插入子查询结果
sql
INSERT INTO <表名> [(<列1>, <列2>, ...)] <子查询>;
-
允许一次性插入多行数据。
-
子查询
SELECT
子句的列数和数据类型必须与INTO
子句匹配。 -
示例 : 对每个专业,求学生的平均年龄,并存入新表
Smajor_age
。sql-- 1. 建表 CREATE TABLE Smajor_age ( Smajor VARCHAR(20), Avg_age SMALLINT ); -- 2. 插入数据 INSERT INTO Smajor_age(Smajor, Avg_age) SELECT Smajor, AVG(extract(year from current_date) - extract(year from Sbirthdate)) FROM Student GROUP BY Smajor;
3.5.2 修改数据 UPDATE
-
语法:
sqlUPDATE <表名> SET <列名> = <表达式> [, <列名> = <表达式>]... [WHERE <条件>];
SET
子句指定要更新的列和新值 。WHERE
子句指定要更新哪些行。如果省略,则更新表中的所有行。
-
示例1 (修改某一个元组的值): 将学生20180001的出生日期改为2000年3月18日。
sqlUPDATE Student SET Sbirthdate='2000-3-18' WHERE Sno='20180001';
-
示例2 (修改多个元组的值):将2020年第1学期选修81002课程所有学生的成绩减少5分。
sqlUPDATE SC SET Grade=Grade-5 WHERE Semester='20201' AND Cno='81002';
-
示例3 (带子查询): 将计算机科学专业所有学生的选课成绩置零。
sqlUPDATE SC SET Grade = 0 WHERE Sno IN (SELECT Sno FROM Student WHERE Smajor = '计算机科学与技术');
3.5.3 删除数据 DELETE
-
语法:
sqlDELETE FROM <表名> [WHERE <条件>];
WHERE
子句指定要删除的行。如果省略,则删除表中的所有行,但表结构依然存在。
-
示例 (带子查询): 删除计算机科学专业所有学生的选课记录。
sqlDELETE FROM SC WHERE Sno IN (SELECT Sno FROM Student WHERE Smajor = '计算机科学与技术');
3.6 空值 (NULL) 的处理
-
含义: 空值表示"不知道"、"不存在"或"无意义"的值。它不是0,也不是空字符串。
-
产生:
INSERT
时未指定值的列。UPDATE
时将某列的值显式设为NULL
。- 外连接中不满足连接条件的元组。
-
判断 : 只能使用
IS NULL
或IS NOT NULL
进行判断 80。不能使用= NULL
。 -
约束:
NOT NULL
约束的列不能取空值。UNIQUE
约束的列不能取空值。- 主键(
PRIMARY KEY
)的任何一列都不能取空值 82。
-
运算与三值逻辑:
- 任何涉及
NULL
的算术运算,结果都是NULL
。 - 任何涉及
NULL
的比较运算,结果都是UNKNOWN
(未知)。 - 引入
UNKNOWN
后,布尔逻辑从二值(TRUE, FALSE)扩展为三值逻辑。 - 三值逻辑真值表 :
- 任何涉及
-
在
WHERE
子句中,只有结果为TRUE
的条件才会被满足,FALSE
和UNKNOWN
都会被过滤掉。如WHERE Grade < 60
会排除掉Grade
为NULL
的记录,因为NULL < 60
的结果是UNKNOWN
。如果要包含这些记录,必须显式地写出OR Grade IS NULL
。
3.7 视图
-
视图在SQL中扮演着至关重要的角色,它对应于关系数据库三级模式结构中的外模式。视图为用户提供了一个看待和操作数据库中数据的窗口。
-
核心特点:
-
虚表: 视图是一个虚拟的表,它是从一个或多个基本表(或者其他视图)中导出的。
-
不存储数据 : 数据库中只存放视图的定义,而不存放视图本身的数据。视图中的数据是在查询时动态生成的。
-
数据同步: 当基本表中的数据发生变化时,从视图中查询出的数据也会随之改变。
-
3.7.1 定义视图
1. 建立视图 CREATE VIEW
-
语法格式:
sqlCREATE VIEW <视图名> [(<列名1>, <列名2>, ...)] AS <子查询> [WITH CHECK OPTION];
-
语法解析:
<视图名>[(<列名>...)]
:- 定义视图的名称。
- 可以为视图的列指定新的名称,当出现以下情况时必须指定所有列名:
- 某个目标列是聚集函数或计算表达式(如
AVG(Grade)
,Age + 1
); - 多表连接时,视图中包含了同名的列。
- 需要为视图的列启用新的、更合适的名称。
- 某个目标列是聚集函数或计算表达式(如
- 如果省略列名列表,则视图的列名由子查询
SELECT
语句的目标列名决定。
AS <子查询>
:- 子查询可以是任意合法的
SELECT
语句,用于定义视图的数据来源和结构。
- 子查询可以是任意合法的
[WITH CHECK OPTION]
:- 这是一个重要的约束选项。如果创建视图时指定了此选项,那么之后通过该视图进行的
INSERT
、UPDATE
、DELETE
操作都必须确保被操作的行满足视图定义中WHERE
子句的条件。
- 这是一个重要的约束选项。如果创建视图时指定了此选项,那么之后通过该视图进行的
-
视图的种类与示例:
-
行列子集视图: 从单个基本表导出,只包含部分行和部分列,但通常保留主码。
sql-- 示例:建立信息管理与信息系统专业学生的视图,并保证通过此视图只能操作该专业的学生 CREATE VIEW IS_Student AS SELECT Sno, Sname, Ssex, Sbirthdate, Smajor FROM Student WHERE Smajor = '信息管理与信息系统' WITH CHECK OPTION; -- 保证更新、插入的行都满足 Smajor = '信息管理与信息系统' 的条件
-
基于多个表的视图:
sql-- 示例:建立信息管理与信息系统专业选修了81001号课程的学生的视图 CREATE VIEW IS_C1 (Sno, Sname, Grade) -- 必须指定列名 AS SELECT Student.Sno, Sname, Grade FROM Student, SC WHERE Student.Sno = SC.Sno AND Smajor = '信息管理与信息系统' AND SC.Cno = '81001';
-
基于视图的视图:
sql-- 示例:在IS_C1视图的基础上,建立成绩在90分以上的学生视图 CREATE VIEW IS_C2 AS SELECT Sno, Sname, Grade FROM IS_C1 WHERE Grade >= 90;
-
带表达式的视图(计算列):
sql-- 示例:定义一个包含学生年龄的视图 CREATE VIEW S_AGE (Sno, Sname, Sage) -- 必须为计算列指定名称 AS SELECT Sno, Sname, (extract(year from current_date) - extract(year from Sbirthdate)) FROM Student;
-
分组视图(带聚集函数):
sql-- 示例:定义一个包含学生学号及其平均成绩的视图 CREATE VIEW S_GradeAVG (Sno, Gavg) -- 必须为聚集函数列指定名称 AS SELECT Sno, AVG(Grade) FROM SC GROUP BY Sno;
-
2. 删除视图 DROP VIEW
-
语法格式:
sqlDROP VIEW <视图名> [CASCADE];
-
功能: 从数据字典中删除指定的视图定义。
-
CASCADE
(级联删除): 如果要删除的视图之上还定义了其他视图,必须使用CASCADE
,它会一并删除所有依赖于该视图的视图。否则,删除操作会失败。-
示例 :因为视图
IS_C2
是基于IS_C1
建立的,所以直接删除IS_C1
会失败。必须使用级联删除 。sqlDROP VIEW IS_C1 CASCADE;
-
3.7.2 查询视图
-
从用户角度: 查询视图的操作与查询基本表完全一样。
-
从DBMS角度 (实现方法):
-
视图消解法:
- 步骤 :
- DBMS检查查询的有效性,并从数据字典中取出视图的定义。
- 将用户对视图的查询改写 成一个等价的、直接对基本表的查询。这是通过将视图定义中的子查询与用户查询进行合并完成的。
- 执行改写后的查询。
- 示例 :
- 用户查询:
SELECT Sno FROM IS_Student WHERE Sage <= 20;
IS_Student
视图定义:SELECT Sno, Sname... FROM Student WHERE Smajor='信息管理与信息系统'
- 消解后 的查询:
SELECT Sno FROM Student WHERE Smajor='信息管理与信息系统' AND Sage <= 20;
- 用户查询:
- 步骤 :
-
视图实体化法:
-
步骤:
- 首先执行视图定义中的
SELECT
语句,生成一个临时表(即实体化的视图)。 - 然后将用户对视图的查询转换为对这个临时表的查询。
- 查询结束后删除该临时表。
- 首先执行视图定义中的
-
如上面的例3.94可以用如下SQL语句完成
sqlSELECT * FROM(SELECT Sno,AVG(Grade) FROM SC GROUP BY Sno) AS S_GradeAVG(Sno,Gavg) WHERE Gavg>=90;
-
-
3.7.3 更新视图
对视图的更新(INSERT
, UPDATE
, DELETE
)最终都会被DBMS转换为对基本表的更新操作。但是,并非所有视图都是可更新的。
- 可更新视图 :
- 通常,行列子集视图(从单个基本表导出,且保留了主码)是可更新的。
- 对这类视图的更新可以被DBMS唯一、有意义地转换为对基本表的操作。
sql
-- 例1:将信息管理与信息系统专业学生视图IS Student中学号为"20180002"的学生姓名改为"刘辰"
CREATE VIEW IS_Student
AS
SELECT Sno,Sname,Ssex,Sbirthdate,Smajor
FROM Student
WHERE Smajor='信息管理与信息系统'
WITH CHECK OPTION;
UPDATE IS_Student
SET Sname='刘辰'
WHERE Sno='20180002';
<======>
UPDATE Student
SET Sname='刘辰'
WHERE Sno='20180002' AND Smajor='信息管理与信息系统';
-- 例2:向信息管理与信息系统专业学生视图IS Student中插入一个新的学生记录,其中学号为"20180207",姓名为"赵新",性别男,出生日期2001年7月19日
CREATE VIEW IS Student
AS
SELECT Sno,Sname,Ssex,Sbirthdate,SmajorFROM Student
WHERE Smajor='信息管理与信息系统'
WITH CHECK OPTION;
INSERT INTO IS_Student
VALUES('20180207''赵新,男''2001-7-19','信息管理与信息系统');
<======>
INSERT INTo Student(Sno,Sname,Ssex,Sbirthdate,Smajor)
VALUES('20180207','赵新','男','2001-7-19,信息管理与信息系统');
-- 例3:删除信息管理与信息系统专业学生视图1SStudent中学号为"20180207"的记录。
CREATE VIEWIS Student
AS
SELECT Sno,Sname,Ssex,Sbirthdate,SmajorFROM StudentWHERE Smajor='信息管理与信息系统'
WITH CHECK OPTION;
DELETE FROM IS_Student
WHERE Sno='20180207';
<======>
DELETE FROM Student
WHERE Sno='20180207'AND Smajor='信息管理与信息系统';
- 不可更新视图 :
- 当对视图的更新操作无法唯一地映射回对基本表的操作时,该视图就是不可更新的。
- 常见不可更新的视图类型 :
- 由两个以上基本表连接导出的视图。
- 视图的字段来自字段表达式、常数或聚集函数 (如
AVG
,MAX
等)。 - 视图定义中含有
GROUP BY
子句。 - 视图定义中含有
DISTINCT
短语。 - 视图定义中有嵌套查询,且内层查询的
FROM
子句中涉及的表也是导出该视图的基本表。 - 在一个不可更新的视图上定义的视图,也是不可更新的。

3.7.4 视图的作用
- 简化用户操作,用户不必关心复杂的查询逻辑(如多表连接、嵌套查询等),只需查询一个简单的视图即可。
- 使用户能以多种角度看待同一数据,可以为不同的用户或应用创建不同的视图,满足他们个性化的数据需求,同时实现了数据共享。
- 对重构数据库提供了一定程度的逻辑独立性。当数据库的逻辑结构(基本表)发生改变时(如一个表被拆分成两个),只要保持视图的定义不变,原先基于视图编写的应用程序就无需修改,从而保护了应用免受底层变化的影响。
- 对机密数据提供安全保护,可以通过视图将表的某些敏感列或行隐藏起来,然后只将视图的权限授予特定用户,这样他们就无法访问到未授权的数据。
- 更清晰地表达查询,对于一些复杂的、需要分步完成的查询,可以先将中间步骤定义为视图,然后基于视图进行最终查询,使整个查询逻辑更加清晰、易于理解。
3.8小结
