database-03-SQL介绍

6371 字
32 分钟
database-03-SQL介绍

数据库系统原理
SQL介绍
2025#

SQL查询语言概览§3.1#

  • Sequel language 是IBM圣何塞研究实验室R系统项目的一部分
    • 后更名: Structured Query Language (SQL)
  • ANSI 和 ISO 标准SQL:
    • SQL-86, SQL-89, SQL-92
    • SQL:1999, SQL:2003, SQL:2008,SQL:2011,SQL:2016
  • 一般商业数据库
    • 支持SQL-92多数特性+部分高版标准特性+部分非标扩展特性
    • 不是所有的例子都能运行在某个特定的系统上
  • DDL – Data Definition language.
    • integrity(完整性) – DDL 包含用于定义完整性约束的功能.
    • View definition(视图定义) – DDL能够定义视图.
    • Authorization(授权) – 能够定义视图和关系的访问权限
  • DML – 查询、插入、删除、修改
    • Transaction control(事务控制) – 控制事务的开始和结束.
    • Embedded SQL(嵌入SQL) 和 Dynamic SQL(动态SQL) - 定义如何将 SQL 语句嵌入到通用编程语言中

SQL的语言特性§3.1.0#

  • 综合统一
    • 非关系模型数据语言
      • 以下语言分别定义
        • 模式DDL(Schema Data Definition Language)
        • 外模式DDL(Subschema Data Definition Language)
        • DSDL(Data Storage Description Language)
        • DML(Data Manipulation Language)
      • 如需修改模式,必须停止当前DB运行、备份、修改、编译、重装数据
    • SQL语言
      • 集DDL、DML、DCL于一体
      • 有能力运行中修改模式
      • 数据结构的单一带来数据操作符的简化
  • 高度非过程化
    • 不再关心存取路径
  • 面向集合的操作方式
    • 不再说明具体处理过程(如存取路径、如何循环处理)
  • 一种语法两种用途
    • 自含式语言
    • 嵌入式语言
  • 语言简洁、易学易用
    • 查询:SELECT
    • 定义:CREATE, DROP, ALTER
    • 操纵:INSERT, UPDATE, DELETE
    • 控制:GRANT, REVOKE

SQL的语法特性§3.1.0#

  • 大小写敏感 关键词、表名(关系名)、字段名(属性名)、内建/自定义函数或过程名
  • ; 作为语句分隔符
    注意:"分隔符"不是"结束符"

SQL数据定义§3.2#

SQL数据定义语言data-definition language(DDL)可定义关系的如下特性:

  • 每个关系relation)的模式schema
  • 每个属性的值域(domain)
  • 完整性约束/Integrity constraints
  • 以及后面会讲到的:
    • 关系的索引定义(indices, index)
    • 每个关系的安全与权限设定(Security and Authorization)
    • 关系的物理存储结构(physical storage structure)

SQL中的基本(数据)类型§3.2.1#

  • char(n)定长串,不足充空
  • varchar(n)变长串,不足截断
  • int 整数(精度长度和具体机器相关,4字节居多)
  • smallint小整数(机器相关,2字节居多)
  • numeric(p,d)定点数,用户指定精度为p位,小数点右侧为d位
  • real,double浮点数和双精度浮点数(精度长度和具体机器相关)
  • float(n) 浮点数,用户指定的精度至少为n位
  • Chapter 4 会涉及更多类型

基本模式(表)定义 - Create Table§3.2.2#

  • create table语法
    CREATE TABLE 表名 ( 字段名 数据类型列级约束条件 ,字段名 数据类型列级约束条件 ,表级约束条件 )
  • CREATE TABLE Cou( --课程表
    Cno int primary key, /*课程号*/
    Cname varchar(16) not null, --课程名
    Cpno int, --先导课程号
    Ccredit int not null, --学分
    foreign key (Cpno) references Cou(cno) --外码约束条件
    )

完整性约束条件§3.2.2#

  • 列级约束条件::= null|not null|unique|primay key|references 表名(字段名)
  • 表级约束条件::=
    unqiue(字段名,字段名) |primary key(字段名,字段名) |foreign key(字段名,字段名) references 表名(字段名,字段名)
  • Create Table Tea( --教师表
    tno int primary key, --工号
    tname varchar(16) not null, --姓名
    phone varchar(13) unique --联系电话
    );
    CREATE TABLE TC( --教师可讲学科表
    tno int references Tea, --教师工号
    cno int references Cou, --课程号
    primary key(tno,cno)
    );
    create table CouHis( --开课记录表
    at_year int, --开课学年
    cno int references Cou, --课程号
    serialNo int, --课序号
    tno int not null references Tea, --主讲老师工号
    weekDay int not null, --星期几
    secNo int not null, --第几大节
    unique(at_year,cno,weekDay,secNo,tno),
    primary key(at_year,cno,serialNo),
    foreign key(tno,cno) references TC
    )
💡大多数数据库的 primary key 隐含 not null 约束
😈SQLite是例外,它要求 not null 必须显式标出

修改表定义(模式)§3.2.2#

  • 语法:ALTER TABLE 表名 修改指令,修改指令
    修改指令::= ADD COLUMN 字段名 数据类型列级约束条件 |DROP COLUMN 字段名 |ALTER COLUMN 字段名 新字段名 数据类型列级约束条件 |DROP CONSTRAINT 约束名称 |ADD CONSTRAINT 约束名称 表级约束条件
  • ALTER TABLE Tea add gender char(2) not null;
    --上方SQL如表中已有数据,会执行失败
    ALTER TABLE Tea add gender char(2) not null default '男';
    --上方SQL成功
    🚨SQL中的字符串常量应使用单引号 ' 而不是双引号 "
    🚨许多数据库不支持删除已有字段,不支持操作约束
    🚨各版本数据库 ALTER TABLE 语法实现不够规范,注意查看数据库手册

SQL查询基本语法§3.3#

  • select from语法
    SELECT ALL|DISTINCT 目标列,目标列 FROM 表名|视图名|子查询,表名|视图名|子查询 WHERE 条件表达式 GROUP BY 字段名,字段名HAVING 条件表达式 ORDER BY 字段名ASC|DESC,字段名ASC|DESC
  • 含义
    • 根据WHERE子句的条件表达式,从FROM子句指定的基本表或视图中找出满足条件的元组
    • SELECT子句中的目标列(表达式),选出元组中的属性值形成结果表
    • 如有GROUP BY子句,则结果按<字段名>的值进行分组(该分量相等的元组为一个组),每组产生结果表中的一条记录(一般会在每组中作用集函数)
    • 如果GROUP子句带HAVING短语,则只有满足指定条件的组才予输出
    • 如有ORDER BY子句,则结果表要按<字段名>分量值的升序或降序排序
  • 要求掌握2~3层的嵌套查询

select子句§3.3.1,§3.4.1#

SELECT ALL|DISTINCT 目标列,目标列 目标列::=字段名AS 别名|计算表达式AS 别名

select子句列出查询结果希望获得的属性,对应关系代数中的投影(π)

--指定目标字段
select Sname,Sage from Stu;
--获取全部字段
select * from Stu;
--计算表达式
select Sname,2025-Sage,'乖学生' from Stu;
--更名(别名)
select Sname as 姓名, 2025-Sage as 出生年, '乖学生' as 属性
from Stu;
--更名(别名)
select Sname as 姓名, strftime('%Y','now')-Sage as 出生年
from Stu;
  • ALL|DISTINCT
    SQL允许查询结果中有重复行
    select dept from Stu; --返回所有行,包括重复行
    select ALL dept from Stu; --同上,ALL是默认值
    select DISTINCT dept from Stu; --去重
    select DISTINCT dept,Sname from Stu; --DISTINCT的作用范围

字符串目标列补充§3.3.1.0#

-- 字符串连接
select sname || '-' || ssex || sage from Stu; --使用连接符
select concat(sname,'-',ssex,sage) from Stu; --使用内建函数
-- 大小写转换,获取字符串长度,抽取子字符串等内建函数
select sname,lower(dept),upper(dept) from Stu; --大小写转换
select cname,length(cname) from Cou; --获取字符串长度
select cname,substr(cname,2,3) from Cou; --抽取子字符串
🚨内建函数并无统一标准,不同数据库需注意查手册

where子句§3.3 §3.4#

WHERE 条件表达式 条件表达式::=逻辑运算逻辑连接符 (NOT条件表达式) 逻辑连接符::=AND|OR 逻辑运算::=NOT (比较运算|范围判断|集合成员|字符串匹配|空值判断)

where子句查询满足条件的元组,对应关系代数中的选择(σ)

比较运算§3.3.1#

比较运算::=目标列比较运算符目标列 比较运算符::= >|<|=|<=|>=|<>|!=|!>|!<
select * from Stu where sage<20;
select * from Stu where sage<20 AND ssex='女';
select * from Stu where NOT sage<20 AND ssex='女';
select * from Stu where NOT(sage<20 AND ssex='女');
🚨比较运算符: >,<,>=,<=,!>,!< 要求<目标列>的值是能够比较大小的(值是有序的)

范围运算§3.4.5#

范围运算::=目标列NOT BETWEEN 目标列 AND 目标列
select * from SC where grade BETWEEN 80 AND 90;
select * from SC where grade>=80 AND grade<=90; --等价
🚨范围判断运算符要求<目标列>的值是有序的
🚨范围区间是闭区间

集合成员§3.8.1#

集合成员::=目标列NOT IN(,|子查询) ::= 和目标列同一个域的任何值
select * from Stu where dept IN('CS','IS');

字符串比较§3.4.2#

  • = < > <= >= <> 都可以用来做字符串比较
    select * from Cou where cname<='数据库';
  • 字符串的有序性问题
    • 字符串的大小比较实质上是字符 编码 值的比较,字符集编码方案不止一个,所以……🙈
    • 数据库能够支持的编码方案也不止一种,所以……😰
🚨字符串的比较默认是`大小写敏感`的
💡很多数据库在字符串常量中使用连续的两个单引号表示一个单引号

字符串匹配§3.4.2#

字符串匹配::=目标列1NOT LIKE 目标列2
目标列2一般是字符串常量,可使用通配符进行字符串匹配
  • %:匹配0到多个任意字符; _:匹配1个任意字符
select * from Stu where sname LIKE '李%';
/* 示例改名
update Stu set sname='李刘晨' where sno='95002';
update Stu set sname='张王敏' where sno='95003'; -- */
insert into Stu values('96001','李%','男',17,'CS');
select * from Stu where sname LIKE '李\%' ESCAPE '\';
--select * from Stu where sname='李%';
🚨字符串匹配运算符要求<目标列>的值是字符串
🚨字符串的匹配默认是`大小写敏感`的

空值判断§3.6#

空值判断::=目标列 ISNOT NULL
select * from Cou where cpno IS NULL;
有的系统允许使用=和<>进行NULL比较完成空值判断,但不推荐

from子句与多关系查询§3.3.1~§3.3.2#

FROM 表名|视图名|子查询,表名|视图名|子查询

from子句指明查询涉及到的表(关系),概念上对应关系代数中的笛卡尔积(×)

  • 只涉及到一张表时,就是单关系查询
  • 涉及到的表(关系)不止一个即多关系查询
select Sname,Cname from Stu,Cou;
笛卡尔积直接使用一般没有意义,通常和`where`配合使用构成连接

from子句中表的别名#

语法: from 原表名as别名

select S.sno,sname,cname,grade
from Stu AS S,SC,Cou AS C
where S.sno=SC.sno and C.cno=SC.cno;
💡有些数据库(比如Oracle)只接受省略关键词`AS`的别名语法

多关系查询中的命名冲突#

/* 反派黑料 */
select sno,sname,cname,grade
from Stu,SC,Cou
where sno=sno and cno=cno;
/* 这才是正面角色 */
select SC.sno,sname,cname,grade
from Stu,SC,Cou
where SC.sno=Stu.sno and SC.cno=Cou.cno;
/* '*'之领域和别名的妙用 */
select Cou.*,CP.cname as 先导课
from Cou,Cou as CP
where Cou.cpno=CP.cno;
💡实际项目中增加和修改字段时,应避免非必要的重复名称

简单SQL查询与关系代数的联系§3.3.2#

SELECT A1, A2, ..., An
FROM T1, T2, ..., Tm
WHERE F;

ΠA1,A2,...AnσF(T1×T2×...×Tm)\Pi_{A1,A2,...An}\sigma_F(T1 \times T2 \times ... \times Tm)

  • 简单SQL查询的逻辑
    1. 通过from子句列出的表(关系)得到笛卡尔积
    2. 使用where子句给出的条件筛选步骤1的结果
    3. 对步骤2得到结果中的每一条元组,根据select子句指明的属性或计算表达式输出

order by子句 - 结果排序§3.4.4#

ORDER BY 字段名ASC|DESC,字段名ASC|DESC

order by子句使输出关系按<字段名>分量值的升序或降序排序

  • ASC - 升序; DESC - 降序
select * from Stu ORDER BY sage; --升序(从小到大)
select * from Stu ORDER BY sage ASC; --同上,ASC是默认值
select * from Stu ORDER BY sage ASC,ssex DESC; --多属性组合排序

聚集函数/Aggregate Functions§3.7.1#

目标列中一种特殊的计算表达式

聚集函数表达式::= SUM(all|distinct字段名) --总和(需数值集合) |AVG(all|distinct字段名) --平均值(需数值) |MAX(all|distinct字段名) --最大值(需有序值) |MIN(all|distinct字段名) --最小值(需有序值) |COUNT(all|distinct*|字段名,字段名) --计数

select MAX(sage),MIN(sage),AVG(sage) from Stu;
/* COUNT那些事 */
select COUNT(*),COUNT(cno) from Cou; --COUNT(*) vs. COUNT(key)
select COUNT(*),COUNT(cpno) from Cou; --遇到null
/* distinct那些事 */
select SUM(sage),SUM(distinct sage) from Stu;
/* AVG=SUM/COUNT ? */
select AVG(sage),SUM(sage),COUNT(sage),
SUM(sage)/COUNT(sage) as avg from Stu;
select AVG(sage),SUM(sage),COUNT(sage),
cast(SUM(sage) as float)/COUNT(sage) as avg from Stu;

group by子句 - 分组§3.7.2#

GROUP BY 字段名,字段名HAVING 条件表达式
  • 在group by指定字段上取值都相同的元组分到同一组
  • 每一组在结果集中产生一条元组(行)
  • 一般会在select子句中使用聚集函数配合group by
/* 列出每门课的课程号、最高分、最低分、平均分 */
select cno,max(grade),min(grade),avg(grade)
from SC GROUP BY cno;
/* 🚫下面是一个错误的SQL,请大家批判地看它 */
select cno,cname,max(grade),min(grade),avg(grade)
from SC,Cou GROUP BY cno;
/* 下面这个例子才是光明正确滴 */
select SC.cno,cname,max(grade),min(grade),avg(grade)
from SC,Cou where SC.cno=Stu.cno
GROUP BY SC.cno,cname;
🚨select子句中的字段只能使用出现在group by后的字段
🚨作为聚集函数的参数没有上述限制

分组的HAVING子句§3.7.3#

select sno,avg(grade) from SC
group by sno HAVING avg(grade)>80;
select sno,avg(grade) as avg from SC
group by sno HAVING avg>80 and sno='95001';
  1. group by指定的属性分组
  2. 然后依据having剔除不符合条件的分组

HAVING vs. WHERE§3.7.3#

select sno,avg(grade) from SC where cno<>2
group by sno HAVING avg(grade)>80;
  1. 首先通过where筛选符合条件的元组
  2. 然后group by指定的属性分组
  3. 接着依据having剔除不符合条件的分组
  4. 最后每组分别计算聚集后产生一条元组

当NULL遇到……§3.6,§3.7.4#

当NULL遇到计算表达式§3.6#

select 5+NULL, 'string' || NULL, concat('A',NULL,'B');
select DISTINCT cpno from Cou;
  • 规则
    💡包含NULL的计算表达的结果是NULL
    💡内建函数试图忽略NULL
    💡DISTINCT判断时,NULL=NULL 处理为 true

当NULL遇到逻辑运算§3.6#

  • 包含null逻辑表达式的结果是unknown
    • 如: 5<null5<null ; null<>nullnull<>null ; null=nullnull=null
  • unknown 参与逻辑运算有以下规则
    • (unknown OR true)=true(unknown~OR~true)=true
    • (unknown OR false)=unknown(unknown~OR~false)=unknown
    • (unknown OR unknown)=unknown(unknown~OR~unknown)=unknown
    • (unknown AND true)=unknown(unknown~AND~true)=unknown
    • (unknown AND false)=false(unknown~AND~false)=false
    • (unknown AND unknown)=unknown(unknown~AND~unknown)=unknown
    • (NOT unknown)=unknown(NOT~unknown)=unknown
  • where/having子句的逻辑表达式如结果为unknown,视为false

当NULL遇到聚集函数§3.7.4#

select avg(cpno),sum(cpno),count(*),count(cpno) from Cou;
  • 规则
    💡所有聚集函数都忽略输入集中的空值,COUNT(*)除外
    💡如输入集为空(或忽略空值后导致空集),COUNT返回0,其他返回null

集合运算§3.5#

  • UNION
    select * from Stu where dept='IS'
    UNION
    select * from Stu where Sage<19;
    -- 等价于:
    select DISTINCT * from Stu where dept='IS' OR Sage<19;
    UNION会自动去掉重复元组(去重);可用 UNION ALL 表示不去重。
    select sno from SC where cno=1
    UNION
    select sno from SC where cno=2
  • INTERSECT
    select * from Stu where dept='IS'
    INTERSECT
    select * from Stu where Sage<=19;
    -- 等价于:
    select DISTINCT * from Stu where dept='IS' AND Sage<=19;
    INTERSECT自动去重;可用 INTERSECT ALL 表示不去重。
    select cpno from Cou INTERSECT select cpno from Cou;
    -- 对比:
    select cpno from Cou;
  • EXCEPT
    select cno from Cou EXCEPT select cpno from Cou;
    select cpno from Cou EXCEPT select 1 from Cou;
    EXCEPT自动去重;可用 EXCEPT ALL 表示不去重。
  • 关于消重(eliminates duplicates)
    • 默认情况下,SQL中的集合运算都会自动消重
    • union all,intersect all,except all表示不消重;
      😰但很多数据库不支持intersect all,except all语法

嵌套子查询/Nested Subqueries§3.8#

  在SQL中,一个SELECT-FROM-WHERE语句称为一个查询块。将一个查询块嵌套在另一个查询块中的查询称为嵌套查询
 ◆ 父查询(外层查询)
 ◆ 子查询(内层查询)/Subquery

select sname from Stu
where sno in(select sno from SC where cno=2);

  嵌套查询使得可以用一系列简单查询构成复杂的查询,从而明显地增强了SQL的查询能力。
  以层层嵌套的方式来构造程序正是 SQL(Structured Query Language)中“结构化”的含义所在

集合成员谓词IN带子查询§3.8.1#

IN带子查询是指父查询与子查询之间用IN/NOT IN进行连接,判断某个属性列值是否在子查询的结果中。
🔎 查询与“刘晨”在同一个系的学生的学号、姓名、所在系

-- 自身连接求解:
SELECT S1.Sno,S1.Sname,S1.dept FROM Stu S1,Stu S2
WHERE S1.dept=S2.dept AND S2.sname='刘晨';
-- 上方语句步骤分解:
SELECT dept FROM Stu WHERE Sname='刘晨';--步骤1
SELECT Sno,Sname,dept FROM Stu WHERE dept IN('CS');--步骤2
-- 子查询求解:
SELECT Sno,Sname,dept FROM Stu WHERE dept IN(
SELECT dept FROM Stu WHERE Sname='刘晨'
)

🔎 查询选修了课程名为“数据库”的学生的学号和姓名

-- 连接求解:
SELECT Stu.Sno,Sname FROM Stu,SC,Cou
WHERE Stu.Sno=SC.Sno AND SC.Cno=Cou.Cno AND Cname='数据库';
-- 子查询求解:
SELECT Sno,Sname FROM Stu WHERE Sno IN(
SELECT Sno FROM SC WHERE Cno IN(
SELECT Cno FROM Cou WHERE Cname='数据库'
)
);

🔎 查询没有选修课程名为“数据库”的学生的学号和姓名

SELECT Sno,Sname FROM Stu WHERE Sno NOT IN(
SELECT Sno FROM SC WHERE Cno IN(
SELECT Cno FROM Cou WHERE Cname='数据库'
)
);

如果不使用子查询

SELECT Stu.Sno,Sname FROM Stu
EXCEPT
SELECT Stu.Sno,Sname FROM Stu,SC,Cou
WHERE Stu.Sno=SC.Sno AND SC.Cno=Cou.Cno AND Cname='数据库';

比较运算带子查询§3.8.2#

父查询与子查询之间用比较运算符(> < = <= >= <>)进行连接

需要注意的是,子查询一定要跟在比较符之后而不是之前

🔎 找出超过平均年龄的学生的学号和姓名

SELECT Sno,Sname FROM Stu WHERE Sage>(
SELECT AVG(Sage) FROM Stu
);

单值(标量/Scalar)比较§3.8.2,§3.8.7#

语法: 比较运算符(子查询)

  • 标量查询/Scalar query 查询结果有且只有一个元组,且元组有且只有一个分量(属性)的查询,如:
    select AVG(Sage) from Stu; --要求至少有一个学生
    select cname from Cou where cno=1; --要求必须有1号课程
    select cno from Cou where cname='数据库'; --这个不一定是标量
  • 计算表达式中的标量查询
    (SELECT count(*) FROM SC) / (SELECT count(*) FROM Stu);
  • 比较运算直接带子查询要确保子查询是标量查询
    SELECT Sno,Sname FROM Stu WHERE Sage>(
    SELECT AVG(Sage) FROM Stu --这个必需是标量查询
    );

集合比较/Set Comparison§3.8.2#

语法: 比较运算符 all|some|any(子查询)

  • all: 子查询结果中所有的值都必须满足条件
  • some等同any: 子查询结果中有一个满足条件即可

🔎 找出年龄比’IS’系的至少一个学生大的学生姓名

select sname from Stu where sage>SOME(
select sage from Stu where dept='IS'
);

🔎 找出年龄比’IS’系的所有学生大的学生姓名

select sname from Stu where sage>ALL(
select sage from Stu where dept='IS'
);
  • 等效语义
    • =some(...) 等同于 in(...)
    • <>some(...) 基本无意义
    • <>all(...) 等同于 not in(...)
    • >some(...) 等同于 >(select min(...) ...)
    • <some(...) 等同于 <(select max(...) ...)
    • >all(...) 等同于 >(select max(...) ...)
    • <all(...) 等同于 <(select min(...) ...)

嵌套查询的求解§3.8.0#

嵌套查询的一般求解§3.8.0#

DBS对嵌套查询的求解方法一般是由里向外处理。即每个子查询在其上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件

SELECT Sname FROM Stu WHERE Sno IN(
SELECT Sno FROM SC WHERE Cno=2;
);
--首先求解子查询:
SELECT Sno FROM SC WHERE Cno=2; --得到{('95001'),('95002')}
--代入上级查询求解:
SELECT Sname FROM Stu WHERE Sno IN('95001','95002');

相关变量/Correlation Variables§3.8.3#

考虑这个查询:🔎找出每个学生超过自己选修课程平均成绩的课程号

SELECT Sno,Cno FROM SC X WHERE Grade>=(
SELECT AVG(Grade) FROM SC Y WHERE Y.Sno=X.Sno
);

子查询的查询条件依赖外层查询属性(在本例中是X.Sno
  这种子查询称为 相关子查询/Correlated Subquery
  这种属性被称为 相关变量/Correlation Variables

相关子查询的求解§3.8.3#

求解相关子查询不能象求解不相关子查询那样,一次将子查询求解出来,然后求解父查询。相关子查询的内层查询由于与外层查询有关,因此必须反复求值

  • 从概念上讲,相关子查询的一般处理过程是
    1. 首先取外层查询中S表的第一个元组
    2. 根据它与内层查询相关的属性值处理内层查询,若WHERE子句返回值为真,则取此元组放入结果表
    3. 然后再检查S表的下一个元组
    4. 重复这一过程,直至S表全部检查完毕

空关系(Empty Relation)测试:EXISTS§3.8.3#

语法: NOTEXISTS(子查询)

  • 判断子查询结果是否空集,代表存在量词 🔎列出所有选修了1号课程的学生姓名
    -- 连接求解:
    SELECT Sname FROM Stu,SC WHERE SC.Sno=Stu.Sno AND Cno=1;
    -- EXISTS子查询求解:
    SELECT Sname FROM Stu WHERE EXISTS(
    SELECT * FROM SC WHERE SC.Sno=Stu.Sno AND Cno=1
    );
  • 等价替换
    一些带EXISTS或NOT EXISTS谓词的子查询不能被其他形式的子查询等价替换,但所有带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换

全称量词的表达§3.8.3.0#

x(P)¬(x(¬P))\forall x(P)\equiv\lnot(\exists x(\lnot P))
选修了全部课程的学生姓名 ≡ 不存在一门课程是没选的

select Sname from Stu where NOT EXISTS(
select * from Cou where NOT EXISTS(
select * from SC where Sno=Stu.Sno and Cno=Cou.Cno
)
);

蕴含的表达§3.8.3.0#

pq¬pqp\to q\equiv\lnot p\lor q
🔎查询至少选修了学生’95002’选修的全部课程的学号

p="学生95002选修了课程y";q="学生x选修了课程y"p="学生95002选修了课程y";\quad q="学生x选修了课程y"

y(pq)=¬(y(¬(pq)))=¬(y(¬(¬pq)))=¬y(p¬q)\forall y(p\to q)\\ =\lnot(\exists y(\lnot(p \to q)))\\ =\lnot(\exists y(\lnot(\lnot p \lor q)))\\ =\lnot\exists y(p \land \lnot q) 不存在课程yy被学生95002选了(pp)而学生xx没有选(¬q\lnot q)

select Sno from Stu X where NOT EXISTS(
select * from Cou Y where Cno in(
select Cno from SC where Sno='95002' --95002选了Y
) and NOT EXISTS(
select * from SC where Sno=X.Sno and Cno=Y.Cno
)
);
select distinct Sno from SC X where NOT EXISTS(
select * from SC Y where Y.Sno='95002' and NOT EXISTS(
select * from SC Z where Z.Sno=X.Sno and Z.Cno=Y.Cno
)
);

重复元组(Duplicate Tuples)存在测试§3.8.4#

语法: NOTUNIQUE(子查询)

  • 判断子查询结果是否有重复元组
    🔎列出最多只被一门课作为先导课的课程号
    select Cno from Cou C where UNIQUE(
    select Cpno from Cou where Cpno=C.Cno
    )
  • 很多RDBS不支持UNIQUE(子查询),可用替代方案
    select Cno from Cou C where 1>=(
    select COUNT(*) from Cou where Cpno=C.Cno
    )

FROM子句中的子查询§3.8.5#

FROM 表名|视图名|子查询,表名|视图名|子查询 子查询::=(SELECT语句)AS 别名
select avg_grade,Cname from Cou,(
select Cno,avg(grade) as avg_grade from SC group by Cno
) AS SC_AVG
where SC_AVG.Cno=Cou.Cno and avg_grade>85

WITH子句§3.8.6#

WITH 模式声明 AS(子查询),模式声明 AS(子查询) 模式声明::=别名(字段名,字段名)

WITH...AS...子句提供一种定义临时关系的方式,作用域仅在同一查询中

WITH SC_AVG(Cno,avg_grade) AS(
select Cno,avg(grade) as avg_grade from SC group by Cno
)
select avg_grade,Cname from Cou,SC_AVG
where SC_AVG.Cno=Cou.Cno and avg_grade>85;

SQL数据修改§3.9#

DELETE - 删除元组§3.9.1#

DELETE FROM 表名WHERE 条件表达式
DELETE FROM Cou; --尝试删除所有课程
DELETE FROM SC WHERE grade<60;
DELETE FROM SC WHERE grade<(select avg(grade) from SC);
-- 疑问:删除会导致平均成绩变化!
-- 实际上会先计算“子查询” 然后再带入求解
🚨注意:FROM后只能有一个关系表!!!

INSERT INTO - 添加元组§3.9.2#

INSERT INTO 表名(字段名,字段名) VALUES(,) |INSERT INTO 表名(字段名,字段名) 子查询
--不指定字段名,values中的值数量和域需匹配create table的字段定义:
INSERT INTO Stu VALUES('95020','陈冬','男',18,'CS');
--指定属性列values中值的数量和顺序需和属性列匹配:
INSERT INTO Stu(Sname,Sno,Ssex) VALUES('辛夏','95021','女');
--指定为空值:
INSERT INTO Stu VALUES('95020','梁秋','男',NULL,NULL);
🚨注意:INSERT INTO后只能有一个关系表!!!
  • 缺省值和默认值
    -- /*假定有表
    create table T(
    id int primary key,
    x int not null DEFAULT 0,
    y int not null,
    z int
    ); -- */
    insert into T(y,id) values(2,1); -- (id,x,y,z)=(1,0,2,null)
    insert into T(id) values(2);-- y报错
    💡未指定某字段值时,首先尝试字段的DEFAULT设定,其次尝试NULL
  • 插入子查询,子查询先完成,然后再插入
    insert into R(A,B,C) select S.*,0 from S;
    🚨子查询的结果的模式必需和指定的目标模式匹配!!!

UPDATE - 修改元素分量§3.9.3#

UPDATE 表名 SET 字段=,字段=WHERE 条件表达式

🚨注意:UPDATE后只允许存在一个关系表!!!
💡可以是常量、计算表达式,也可以是标量查询

UPDATE SC SET grade=grade+10; --分数可能超100
--两条语句分段改分,不同顺序会导致不同结果:
UPDATE SC SET grade=grade*1.05 WHERE grade>80;
UPDATE SC SET grade=grade*1.25 WHERE grade<=80;
--使用标量子查询赋值:
update Stu set Sage=(SELECT avg(sage) FROM Stu)+1;

条件修改/Case Statement for Conditional Updates* §3.9.3#

update SC set grade=CASE
WHEN grade<=90 THEN grade*1.05
WHEN grade<=80 THEN grade*1.25
ELSE grade*1.01
END;

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
database-03-SQL介绍
https://meteorfate-github-io.pages.dev/posts/03-sql介绍/
作者
METEORfate
发布于
2026-03-25
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
METEORfate
Somebody
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
15
分类
3
标签
20
总字数
84,986
运行时长
0
最后活动
0 天前

目录