数据操纵语言
常见SQL数据操作语句(DML)
| 操作 | 说明 | 示例 |
|---|---|---|
| Insert | 向表中添加新记录 | INSERT INTO users (name) VALUES ('John') |
| Update | 修改指定记录的字段值 | UPDATE users SET name='Mike' WHERE id=1 |
| Delete | 从表中删除符合条件的记录 | DELETE FROM users WHERE id=1 |
| Select | 查询符合条件的记录 | SELECT * FROM users WHERE age>18 |
SQL查询
通过一次查询,从数据库中的一个或多个表中检索符合条件的数据。
SELECT 目标列列表 * 表示 "该关系的所有属性"。
FROM 关系列表
WHERE 条件
例如:
SELECT name
FROM Beers
WHERE manf = 'Anheuser-Busch';
注:字符串需用单引号括起
列操作重命名
若希望查询结果中的属性使用不同名称,可通过AS <新名称>为属性重命名。
SELECT name AS beer
FROM Beers
WHERE manf='Anheuser-Busch';
如此,查询结果中 "name" 属性将显示为 "beer"
表达式
SELECT bar, beer, price*120 AS pricelnYen FROM Sells;
(将价格换算为日元,结果列命名为 pricelnYen)
where中表达式
SELECT price
FROM Sells
WHERE bar = 'Joe''s Bar' AND beer = 'Bud';
WHERE 子句中的条件可使用逻辑运算符、比较运算符
字符串中两个连续的单引号('')表示一个单引号(如示例中 'Joe''s Bar' 表示 Joe's Bar)
模糊匹配
% 表示任意字符串(长度可为 0)
_ 表示任意单个字符
例如:SELECT name
FROM Drinkers
WHERE phone LIKE '138%';
NULL值
SQL 关系中的元组(行)可包含一个或多个 NULL 值(空值)。
含义(取决于上下文)
- 值未知:例如,已知乔的酒吧有地址,但具体地址未知
- 值不适用:例如,未婚人士的 "配偶" 属性值
- 值保密:无权获取该值(如电话号码)
判断属性 x 是否为 NULL,需使用表达式 x IS NULL
SQL 中产生 NULL 值的常见方式:
- 外连接(Outer join)会产生 NULL 值(SQL 支持外连接)。
- 元组的交集运算也可能产生 NULL 值。
NULL操作规则
- 对 NULL 值进行算术运算(如 ×、+),结果仍为 NULL。
示例:NULL + 3 非法;x + 3(若 x 为 NULL,结果为 NULL)。 - 对 NULL 值进行比较运算(如 =、>),结果为 UNKNOWN(未知)------ 这是与 TRUE(真)、FALSE(假)并列的第三种真值。
- NULL 不是常量,不能直接作为操作数使用。
错误写法:NULL = 3;正确写法:判断 x 是否为 NULL 需使用表达式x IS NULL。
三值逻辑
SQL 条件逻辑为三值逻辑:TRUE(1)、FALSE(0)、UNKNOWN(0.5)
如何在三值逻辑中使用 AND、OR、NOT 运算符?
答:将真值映射为数值:TRUE=1、FALSE=0、UNKNOWN=0.5。
AND(与):取最小值(MIN)
OR(或):取最大值(MAX)
NOT(非):1 - 原值(如 NOT TRUE = 0,NOT UNKNOWN = 0.5)
WHERE length <= 120 OR length > 120;
若 length 为 NULL,length <= 120 和 length> 120 的结果均为 UNKNOWN,OR 运算后仍为 UNKNOWN,因此不会被包含在结果中(查询结果仅包含 WHERE 子句结果为 TRUE 的元组)
结果排序
ORDER BY 子句紧跟在 WHERE 子句之后,语法如下:
ORDER BY <属性列表> ASC/DESC
其中:ASC(ascending):升序排序(默认,可省略);DESC(descending):降序排序。
示例:
SELECT empname
FROM Employee
WHERE deptno = 'd2'
ORDER BY empno DESC;
(查询部门编号为 d2 的员工姓名,按员工编号降序排序)
关系组合与连接
如何在一个 SQL 查询中关联多个关系?
解决方案
在 FROM 子句中列出每个关系,SELECT 和 WHERE 子句可引用所有关系的属性。
查询《星球大战》(Star Wars)的制片人姓名
表结构:
Movies (title, year, length, genre, studioName, producerC#)(电影表,制片人编号为 producerC#)
MovieExec (name, address, cert#, netWorth)(电影高管表,证书编号为 cert#,与制片人编号关联)
SELECT name
FROM Movies, MovieExec
WHERE title = 'Star Wars' AND producerC# = cert#;
属性歧义消除
查询涉及多个关系,且其中两个或多个关系包含同名属性。
在属性名前加上关系名和点号(.)
SELECT MovieStar.name, MovieExec.name
FROM MovieStar, MovieExec
WHERE MovieStar.address = MovieExec.address;
显示元组变量
查询需涉及同一关系的多个元组(如查询同一关系中满足条件的元组对)。
在 FROM 子句中多次列出同一关系,并为每个实例定义 "别名"(即元组变量),用于区分不同实例。
查询地址相同且姓名不同的明星对
SELECT Star1.name, Star2.name
FROM MovieStar Star1, MovieStar Star2
WHERE Star1.address = Star2.address
AND Star1.name < Star2.name;
查询同一制造商生产的啤酒对:
FROM Beers b1, Beers b2
WHERE b1.manf = b2.manf
连接表达式
SQL 支持多种基于 JOIN 运算符的关系构造方式,包括乘积(product)、theta 连接、自然连接(natural join)和外连接(outer join)。连接结果可直接作为查询结果,或作为 FROM 子句中的子查询。
交叉连接(笛卡尔积)
即笛卡尔积(Cartesian Product),将两个关系的所有元组两两组合。
Movies CROSS JOIN Starsin;
结果:包含 9 列(电影表的 6 个属性 + 出演表的 3 个属性),属性可通过 "关系名。属性名" 引用(如 Movies.title)。
theta连接
使用 JOIN 和 ON 关键字,先对两个关系做笛卡尔积,再按 ON 后的条件筛选。
-- 基础 Theta 连接(含冗余属性)
Movies JOIN Starsin ON title = movieTitle AND year = movieYear;
-- 去除冗余属性,只保留需要的列
SELECT title, year, length, genre, studioName, producerC#, starName
FROM Movies JOIN Starsin ON title = movieTitle AND year = movieYear;
自然连接
与 Theta 连接的区别
连接条件:自动对两个关系中所有同名属性进行等值连接,无其他条件
结果处理:删除同名属性中的重复列(仅保留一列)
MovieStar NATURAL JOIN MovieExec;
外连接
在普通连接结果的基础上,补充 "悬挂元组"(未匹配到的元组),并用 NULL 填充未匹配的属性。
全外连接(FULL OUTER JOIN):补充两个关系中所有未匹配的元组
左外连接(LEFT OUTER JOIN):补充左表中未匹配的元组
右外连接(RIGHT OUTER JOIN):补充右表中未匹配的元组
例子:MovieStar NATURAL FULL OUTER JOIN MovieExec;
结果包含三类元组:
既是明星又是高管的人(所有属性非 NULL)。
是明星但非高管的人(高管表相关属性为 NULL)。
是高管但非明星的人(明星表相关属性为 NULL)。
集合运算
对应关系代数中的并(∪)、交(∩)、差(-)运算,要求两个关系的属性列表和属性类型完全一致。
UNION:并集(合并两个结果集,去除重复元组)
INTERSECT:交集(仅保留两个结果集共有的元组)
EXCEPT:差集(保留第一个结果集中存在、第二个结果集中不存在的元组)
MovieStar(name, address, gender, birthdate)
MovieExec(name, address, cert#, netWorth)
查询既是女性明星又是净资产超百万的制片人的姓名和地址
(SELECT name, address FROM MovieStar WHERE gender = 'F')
INTERSECT
(SELECT name, address FROM MovieExec WHERE netWorth > 1000000);
查询是电影明星但非制片人的姓名和地址。
(SELECT name, address FROM MovieStar)
EXCEPT
(SELECT name, address FROM MovieExec);
子查询
作为另一个查询一部分的查询称为子查询,子查询可嵌套多层,用法如下:
- 返回单个常量,用于 WHERE 子句中与其他值比较。
- 返回关系,用于 WHERE 子句的条件判断。
- 出现在 FROM 子句中,后跟元组变量(代表子查询结果的元组)。
- 通过 UNION、INTERSECT、EXCEPT 与其他查询组合。
查询销售米勒(Miller)啤酒且价格与乔的酒吧(Joe's Bar)销售百威(Bud)价格相同的酒吧。
已知表:Sells (bar, beer, price)(销售表)
SELECT bar
FROM Sells
WHERE beer = 'Miller' AND price =
(SELECT price
FROM Sells
WHERE bar = 'Joe''s Bar' AND beer = 'Bud');
1.产生标量值的子查询
如何查找《星球大战》的制片人?
Movies(title, year, length, genre, studioName, producerC#) MovieExec(name, address, cert#, netWorth)
第一种:
SELECT name
FROM Movies, MovieExec
WHERE title = 'Star Wars' AND producerC# = cert#;
子查询第二种:
SELECT name
FROM MovieExec
WHERE cert#=
(SELECT producerC#
FROM Movies
WHERE title = 'Star Wars' );
2.涉及关系的条件
有多个 SQL 运算符可应用于关系 R 并产生布尔结果。
关系 R 必须表示为子查询。
运算符:EXISTS、IN、ALL 和 ANY
在以下表达式中,S 是标量值,子查询 R 需产生单列表关系。
EXISTS R(R 非空则为真)
S IN R(S 等于 R 中某个值则为真)
S > ALL R(S 大于单列表关系 R 中所有值则为真)
S > ANY R(S 大于单列表关系 R 中至少一个值则为真)
EXISTS、IN、ALL 和 ANY 运算符可通过在整个表达式前加 NOT 进行否定。
EXISTS
查找由制造商独家生产的啤酒。啤酒(名称,制造商)
SELECT name
FROM Beers b1
WHERE NOT EXISTS
(SELECT * FROM Beers
WHERE manf = b1.manf AND name <> b1.name);
IN
S IN R:当且仅当 S 等于关系 R 中的某个值时为真。
SELECT *
FROM Beers
WHERE name IN
(SELECT beer
FROM Likes
WHERE drinker = 'Fred');
ALL
S > ALL R:当且仅当 S 大于单列表关系 R 中的每个值时为真
查找售价最高的啤酒。销售(酒吧,啤酒,价格)
SELECT beer
FROM Sells
WHERE price >= ALL
(SELECT price
FROM Sells);
ANY
S > ANY R:当且仅当 S 大于单列表关系 R 中的至少一个值时为真
查找非最低价销售的啤酒。销售(酒吧,啤酒,价格)
SELECT beer
FROM Sells
WHERE price > ANY
(SELECT price
FROM Sells);
关于IN问题

Q1 执行 R 和 S 的笛卡尔积后,筛选出 R.b = S.b 的元组,可能产生重复的 a 值(因 S 中同一 b 可能对应多个元组)。
Q2 筛选出 R 中 b 值存在于 S 的 b 列中的 a 值,结果中 a 值无重复(默认按集合语义)

关于ALL和ANY问题

3.嵌套子查询
查找被用于两部及以上电影的片名。
SELECT title
FROM Movies Old
WHERE year < ANY
(SELECT year
FROM Movies
WHERE title = Old.title);
4.FROM 子句中的子查询
查找哈里森・福特参演电影的制片人。
Movies(title,year, length, genre,studioName,producerC#) MovieExec(name, address,cert, netWorth)
Starsin(movieTitle, movieYear, starName)
SELECT name
FROM MovieExec,
(SELECT producerC#
FROM Movies,Starsin
WHERE
title = movieTitle
AND year = movie ear
AND starName = 'Harrison Ford') AS Prod
WHERE cert#= Prod.producerC#;
步骤 1:子查询(内层 SELECT)------ 找到哈里森・福特参演电影的制片人证书编号
先做Movies和Starsin的笛卡尔积,再通过title=movieTitle+year=movieYear筛选出 "电影 - 参演明星" 的有效关联记录;再过滤出starName为 "Harrison Ford" 的记录,最终得到这些电影对应的producerC#(制片人证书编号);给这个子查询结果起别名Prod,方便外层查询引用。
步骤 2:外层关联 ------ 将制片人证书编号匹配到制片人姓名
SELECT name
FROM MovieExec, Prod
WHERE cert# = Prod.producerC#
做MovieExec(制片人信息表)和Prod(哈里森・福特参演电影的制片人证书编号)的笛卡尔积;
通过cert#=Prod.producerC#筛选出 "证书编号匹配" 的记录,最终提取name字段(制片人姓名)。
消除重复项
SELECT-FROM-WHERE 的默认语义是多重集合(bag);多重集合允许重复元组,而集合(set)不允许。
若不希望出现重复项,可在 SELECT 后加 DISTINCT 强制使用集合语义(执行成本较高)。
集合运算中的重复项
UNION、INTERSECT 和 EXCEPT 的默认语义是集合(set),集合不允许重复元组(与多重集合不同)。
若要保留重复项,需在 UNION、INTERSECT 或 EXCEPT 后加关键字 ALL。
(SELECT title, year FROM Movies)
UNION ALL
(SELECT movieTitle AS title, movieYear AS year FROM Starsin);
聚合函数
SQL 提供五种聚合运算符:SUM(求和)、AVG(求平均)、MIN(求最小值)、MAX(求最大值)和 COUNT(计数)。
运算符应用于关系的一列。
COUNT (*) 用于统计关系中的元组数量。
c
-- 计算 Bud 啤酒的平均售价
SELECT AVG(price) FROM Sells WHERE beer = 'Bud';
-- 统计销售 Bud 啤酒的元组数量
SELECT COUNT(*) FROM Sells WHERE beer = 'Bud';
-- 统计 Bud 啤酒的不同售价数量
SELECT COUNT(DISTINCT price) FROM Sells WHERE beer = 'Bud';
分组
通过 GROUP BY 子句对元组分组,该子句位于 WHERE 子句之后。
SELECT-FROM-WHERE-GROUP BY(属性列表)
SELECT-FROM-WHERE 子句的结果按分组属性列表的值分组。聚合运算在每个组内执行。
查找每种啤酒的平均售价。
销售(酒吧,啤酒,价格)
SELECT beer, AVG(price)
FROM Sells
GROUP BY beer;
查找每位饮酒者常去的酒吧中 Bud 啤酒的平均售价。
销售(酒吧,啤酒,价格)
常去(饮酒者,酒吧)
SELECT drinker, AVG(price)
FROM Frequents, Sells
WHERE beer = 'Bud' AND Frequents.bar = Sells.bar
GROUP BY drinker;
多关系查询步骤
笛卡尔积 → FROM 子句
筛选 → WHERE 子句
分组 → GROUP BY 子句
生成结果 → SELECT 子句
含聚合函数的 SELECT 列表
SELECT 子句中的每个元素必须要么是聚合函数,要么出现在 GROUP BY 子句中。
-- 错误示例
SELECT bar, MIN(price)
FROM Sells
WHERE beer = 'Bud';
这个查询没有 GROUP BY 子句,却同时选择了非聚合列 bar 和聚合函数 MIN(price)。假设表中有多个酒吧销售 Bud 啤酒,MIN(price) 会返回所有 Bud 中的最低价格,但 bar 应该显示哪一个酒吧呢?数据库无法确定,因此这个查询是无效的,会报错。
-- 正确示例
SELECT bar
FROM Sells
WHERE price = (SELECT MIN(price) FROM Sells WHERE beer = 'Bud');
分组中的 NULL 值
当元组包含 NULL 值时,需遵循以下规则:
1.任何聚合运算都会忽略 NULL 值。COUNT (*) 始终计算关系中的元组数量,而 COUNT (A) 计算属性 A 非 NULL 值的元组数量。
2.分组时,NULL 被视为普通值。
3.对空多重集合执行除 COUNT 外的任何聚合运算,结果为 NULL;空多重集合的 COUNT 结果为 0。
举个例子
| id(学生编号) | 姓名 | 数学成绩(0-100) | 语文成绩(0-100) |
|---|---|---|---|
| 1 | 张三 | 80 | 85 |
| 2 | 李四 | NULL(缺考) | 78 |
| 3 | 王五 | 90 | 92 |
| 4 | 赵六 | NULL(未录入) | 88 |
1.聚合运算忽略 NULL,COUNT () 和 COUNT (A) 有区别:
SUM(math) → 80+90=170(跳过李四、赵六的 NULL);
COUNT( ) → 4(总共 4 行,不管列值是不是 NULL);
COUNT(math) → 2(只有张三、王五的 math 非 NULL);
2.分组时,NULL 被视为普通值
SELECT math, COUNT(*) FROM score GROUP BY math;
| math | COUNT(*) |
|---|---|
| 80 | 1 |
| NULL | 2 |
| 90 | 1 |
3.空多重集合的聚合结果(COUNT 除外为 NULL,COUNT 为 0)
对 "空集合" 用SUM/AVG/MAX/MIN等(除了 COUNT),结果是NULL(因为 "无数据可算,结果未知");
对 "空集合" 用COUNT,结果是 0(因为 "数个数,没有就是 0 个")。
HAVING 子句
有时我们希望根据组本身的聚合属性选择组。HAVING 子句用于对组进行筛选。
SELECT beer, AVG(price)
FROM Sells
GROUP BY beer
HAVING COUNT(*) >= 3 OR beer IN
(SELECT name
FROM Beers
WHERE manf = 'Busch');
数据库修改
| 功能 | SQL语句 |
|---|---|
| 向关系中插入元组 | INSERT INTO(插入) |
| 从关系中删除特定元组 | DELETE FROM(删除) |
| 更新现有元组的某些组件的值 | UPDATE...SET...WHERE(更新) |
插入
若属性列表未包含关系 R 的所有属性,则创建的元组中缺失属性取默认值。
INSERT INTO R(A1, ..., An) VALUES (v1, ..., vn);
示例 1:指定属性列表
INSERT INTO Starsin(movieTitle, movieYear, starName)
VALUES('The Maltese Falcon', 1942, 'Sydney Greenstreet');
示例 2:省略属性列表(按关系定义顺序赋值)
INSERT INTO Starsin
VALUES('The Maltese Falcon', 1942, 'Sydney Greenstreet');
示例 3:通过子查询插入一组元组
INSERT INTO Studio(name)
SELECT DISTINCT studioName
FROM Movies
WHERE studioName NOT IN
(SELECT name FROM Studio);
(新元组仅包含 name 属性的值,address 和 presC# 取值为 NULL)
示例 4:创建新表并插入数据
创建 Sally 潜在好友表 ------ 与 Sally 常去同一家酒吧的人
Frequents(drinker, bar)
CREATE TABLE PotBuddies(name char(30));
INSERT INTO PotBuddies
(SELECT DISTINCT d2.drinker
FROM Frequents d1, Frequents d2
WHERE d1.drinker = 'Sally' AND d2.drinker <> 'Sally'
AND d1.bar = d2.bar);
也可以使用子查询
SELECT DISTINCT drinker
FROM Frequents
WHERE drinker <> 'Sally' -- 排除Sally自己
AND bar IN (
-- 子查询:先拿到Sally去过的所有酒吧
SELECT bar FROM Frequents WHERE drinker = 'Sally'
);
删除
DELETE FROM R WHERE condition;
示例 1:删除单个元组
DELETE FROM Starsin
WHERE movieTitle = 'The Maltese Falcon' AND movieYear = 1942 AND starName = 'Sydney Greenstreet';
示例 2:删除多个元组
DELETE FROM MovieExec
WHERE netWorth < 10000000; -- 删除净资产低于 1000 万的电影高管
示例 3:清空关系
DELETE FROM Likes; -- 清空 Likes 关系中的所有元组
更新
UPDATE R
SET <新值赋值>
WHERE <条件>;
每个新值赋值格式为:属性 = 表达式,多个赋值用逗号分隔
示例 1:更新单个属性
Sells(bar, beer, price)
UPDATE Sells
SET price = 4.00
WHERE price > 4.00; -- 将售价高于 4.00 的记录调整为 4.00
示例 2:结合子查询更新
MovieExec(name, address, cert#, netWorth)
Studio(name, address, presC#)
UPDATE MovieExec
SET name = 'Pres.' || name -- 姓名前添加 "Pres." 前缀
WHERE cert# IN
(SELECT presC# FROM Studio); -- 仅更新制片厂总裁的记录