1-8 EXISTS谓词的用法
SQL中的谓词逻辑
SQL的基础理论:
- 集合论
- 谓词逻辑
理论篇
谓词:如"=、<、>、BETWEEN、LIKE、IN、IS NULL"等。
谓词逻辑提供谓词为了判断命题的真假。在关系数据库里,表中的一行可以看作是一个命题。表常常被认为是行的集合,但从谓词逻辑的观点看,可以认为是命题的集合。
数据库这种叫法有点名不副实,它存储的与其说是数据,还不如说是命题(Database in Depth: Relational Theory for Practitioners , O’Reilly Media,2005)
实体的阶层
与"="、"BETWEEN"相比,EXISTS的用法区别在于“谓词的参数可以取什么值”。
“x = y ”或“x BETWEEN y ”等谓词可以取的参数是单一值,我们称之为标量值。而EXISTS可以取的是参数是行数据的集合。由此可见,EXISTS的特殊性在于输入值的阶数。=或者BETWEEN等输入值为一行的谓词叫作“一阶谓词”。而EXISTS输入值为行的集合的谓词叫作“二阶谓词”。
实践篇
查询表中“不”存在的数据
应用场景:查找没有参加某次会议的人,目标结果如下所示,是各次会议缺席者的列表。
问题:并不是根据存在的数据查询“满足这样那样条件”的数据,而是查询“数据是否存在”。即所谓“二阶查询”。
思路:假设所有人都参加了全部会议,生成一个集合,然后减去实际参加会议的人。
(1) 所有人都参加了全部会议的集合可以通过交叉连接求得。
SELECT DISTINCT
M1.meeting, M2.person
FROM
Meetings M1
CROSS JOIN
Meetings M2
(2) 从这张表减去实际参会者的集合。
/* 用于求出缺席者的SQL语句(1):存在量化的应用 */
SELECT DISTINCT M1.meeting, M2.person
FROM Meetings M1 CROSS JOIN Meetings M2
WHERE NOT EXISTS
(SELECT *
FROM Meetings M3
WHERE M1.meeting = M3.meeting
AND M2.person = M3.person);
还可以用集合论的方法来解答。即使用差集运算。
/* 用于求出缺席者的SQL语句(2):使用差集运算 */
SELECT M1.meeting, M2.person
FROM Meetings M1, Meetings M2
EXCEPT
SELECT meeting, person
FROM Meetings;
NOT EXISTS 具备了差集运算的功能。
全称量化(1)
“肯定”<=>“双重否定”
“所有的行都xx”到其双重否定“不xx的行一行都不存在”
应用场景:查询出“所有科目分数都是50分以上的学生”
思路:将查询条件“所有科目分数都在50分以上”转换成双重否定“没有一个科目分数不满50分”
/* 全称量化(1):习惯“肯定<=>双重否定”之间的转换 */
SELECT DISTINCT student_id
FROM TestScores TS1
WHERE NOT EXISTS /* 不存在满足以下条件的行 */
(SELECT *
FROM TestScores TS2
WHERE TS2.student_id = TS1.student_id
AND TS2.score < 50); /* 分数不满50分的科目 */
查询满足下列条件的学生:
01 数学分数在80分以上
02 语文分数在50分以上
换言之,查询“某个学生的所有行数据中,如果科目是数学,则分数在80分以上;如果科目是语文,则分数在50分以上”
针对同一个集合内的行数据进行了条件分支后的全称量化。
/* 全称量化(1):习惯“肯定<=>双重否定”之间的转换 */
SELECT
student_id
FROM
TestScores TS1
WHERE
subject IN ('数学' , '语文')
AND NOT EXISTS( SELECT
*
FROM
TestScores TS2
WHERE
TS2.student_id = TS1.student_id
AND 1 = CASE
WHEN subject = '数学' AND score < 80 THEN 1
WHEN subject = '语文' AND score < 50 THEN 1
ELSE 0
END)
GROUP BY student_id
HAVING COUNT(*) = 2
全称量化(2)
应用场景:查询哪些项目已经完成到了工程1。
思路:
(1) HAVING 子句用面向集合的方法
针对每个项目,将工程编号为1以下且状态为“完成”的行数,和工程编号大于1且状态为“等待”的行数加在一起,如果和等于该项目数据的总行数,则该项目符号查询条件。
/* 查询完成到了工程1的项目:面向集合的解法 */
SELECT project_id
FROM Projects
GROUP BY project_id
HAVING COUNT(*) = SUM(CASE WHEN step_nbr <= 1 AND status = '完成' THEN 1
WHEN step_nbr > 1 AND status = '等待' THEN 1
ELSE 0 END);
(2) 全称量化命题
“某个项目的所有行数据中,如果工程编号是1以下,则该工程已完成;如果工程编号比1大,则该工程还在等待”
这个条件仍然可以用 CASE 表达式来描述。
step_status = CASE WHEN step_nbr <= 1
THEN '完成'
ELSE '等待' END
/* 查询完成到了工程1的项目:谓词逻辑的解法 */
SELECT *
FROM Projects P1
WHERE NOT EXISTS
(SELECT status
FROM Projects P2
WHERE P1.project_id = P2. project_id /* 以项目为单位进行条件判断 */
AND status <> CASE WHEN step_nbr <= 1 /* 使用双重否定来表达全称量化命题 */
THEN '完成'
ELSE '等待' END);