1-10 HAVING子句又回来了
本节 主要学习HAVING子句的应用技巧,“调查集合自身性质”
应用场景:查找可以出勤的队伍。即队伍里所有队员都处于“待命”状态。
(1)使用NOT EXISTS 表达式
/* 用谓词表达全称量化命题 */
SELECT team_id, member
FROM Teams T1
WHERE NOT EXISTS
(SELECT *
FROM Teams T2
WHERE T1.team_id = T2.team_id
AND status <> '待命' );
(2)使用HAVING子句
/* 用集合表达全称量化命题(1) */
SELECT
team_id
FROM
Teams
GROUP BY team_id
HAVING COUNT(*) = SUM(CASE
WHEN status = '待命' THEN 1
ELSE 0
END)
思考:第一步使用GROUP BY子句将Teams集合以队伍为单位划分成几个子集。目标集合是S3和S4,那么只有这两个集合而其他集合没有的特征是,处于“待命”状态的数据行数与集合中数据总行数相等。这个条件可以用CASE表达式来表达,状态为"待命"的情况下返回1,其他情况返回0。这里使用的是特征函数的方法。
/* 用集合表达全称量化命题(2) */
SELECT
team_id
FROM
Teams
GROUP BY team_id
HAVING MAX(status) = '待命'
AND MIN(status) = '待命'
这个SQL语句表示,集合中如果元素最大值和最小值相等,那么这个集合中肯定只有一种值。
/* 列表显示各个队伍是否所有队员都在待命 */
SELECT team_id,
CASE WHEN MAX(status) = '待命' AND MIN(status) = '待命'
THEN '全都在待命'
ELSE '队长!人手不够' END AS status
FROM Teams
GROUP BY team_id;
单重集合与多重集合
1-7节中说过,关系数据库中的集合是允许重复数据存在的多重集合。
允许循环插入和频繁读写的表中有可能产生重复数据。在定义表时加入唯一性约束可以预防表中产生重复数据,但是有些情况下根据具体的业务需求不同,产生重复数据也是合理的。
应用场景:假设有一张管理各个生产地的材料库存的表。
思路:先分析满足条件的生产地具有哪些特征。一个生产地对应着多条数据,因此“生产地”这一实体在表中是以
集合的形式,而不是以元素的形式存在的。通过使用GROUP BY 子句将集合划分为若干个子集。
目标集合满足而其他集合不满足的条件是,“排除掉重复元素后和排除重复元素前元素个数不相同”。
/* 选中材料存在重复的生产地 */
SELECT center
FROM Materials
GROUP BY center
HAVING COUNT(material) <> COUNT(DISTINCT material);
和前面一样,我们可以把条件移到SELECT 子句中,这样就能在结果中清晰地看到各个生产地是否存在重复材料了。
/* 列表显示是否存在重复 */
SELECT center,
CASE WHEN COUNT(material) <> COUNT(DISTINCT material)
THEN '存在重复'
ELSE '不存在重复' END AS status
FROM Materials
GROUP BY center;
在数学中,通过 GROUP BY 生成的子集有一个对应
的名字,叫作划分(partition)。它是集合论和群论中的重要概念,指的是将某个集合按照某种规则进行分割后得到的子集。这些子集相互之间没有重复的元素,而且它们的并集就是原来的集合。这样的分割操作被称为划分操作。
HAVING 改为EXISTS SQL 语句为:
/* 存在重复的集合:使用EXISTS */
SELECT center, material
FROM Materials M1
WHERE EXISTS
(SELECT *
FROM Materials M2
WHERE M1.center = M2.center
AND M1.receive_date <> M2.receive_date
AND M1.material = M2.material);
相反,如果想要查出不存在重复材料的生产地有哪些,只需要把EXISTS 改为 NOT EXISTS 就可以了。
寻找缺失的编号:升级版
1-4 节中介绍查询数列的缺失编号的查询语句。
/* 如果有查询结果,说明存在缺失的编号*/
SELECT '存在缺失的编号’ AS gap
FROM SeqTbl
HAVING COUNT(*)<>MAX(seq)
这条SQL语句有一个前提条件,即数列的起始值必须是1。对于起始值不一定为1的情况,数列包含的元素的个数应该是“最大值-最小值+1”。
/* 如果有查询结果,说明存在缺失的编号:只调查数列的连续性*/
SELECT '存在缺失的编号’ AS gap
FROM SeqTbl
HAVING COUNT(*)<>MAX(seq)-MIN(seq)+1
如果不论是否存在缺失的编号,都想要返回结果,那么只需要像下面这样把条件写到SELECT里就可以了。
/* 不论是否存在缺失的编号都返回一行结果 */
SELECT CASE WHEN COUNT(*) = 0
THEN '表为空'
WHEN COUNT(*) <> MAX(seq) - MIN(seq) + 1
THEN '存在缺失的编号'
ELSE '连续' END AS gap
FROM SeqTbl;
对查找最小的缺失,
/* 查找最小的缺失编号:表中没有1时返回1 */
SELECT CASE WHEN MIN(seq) > 1 /* 最小值不是1时→返回1 */
THEN 1
ELSE (SELECT MIN(seq +1) /* 最小值是1时→返回最小的缺失编号 */
FROM SeqTbl S1
WHERE NOT EXISTS
(SELECT *
FROM SeqTbl S2
WHERE S2.seq = S1.seq + 1))
END AS min_gap
FROM SeqTbl;
为集合设置详细的条件
使用 CASE 表达式来描述特征函数。
应用场景:学生考试成绩表。
(1)查询出75%以上的学生分数都在80分以上的班级。
/* 75%以上的学生分数都在80分以上的班级 */
SELECT
class
FROM
TestResults
GROUP BY class
HAVING COUNT(*) * 0.75 <= SUM(CASE
WHEN score >= 80 THEN 1
ELSE 0
END)
(2) 查询分数在50分以上的男生的人生比分数在50分以上的女生的人数多的班级。
/* 分数在50分以上的男生的人数比分数在50分以上的女生的人数多的班级 */
SELECT
class
FROM
TestResults
GROUP BY class
HAVING SUM(CASE
WHEN score >= 50 AND sex = '女' THEN 1
ELSE 0
END) < SUM(CASE
WHEN score >= 50 AND sex = '男' THEN 1
ELSE 0
END)
(3)查询女生平均分比男生平均分高的班级
/* 比较男生和女生平均分的SQL语句(1):对空集使用AVG后返回0 */
SELECT
class
FROM
TestResults
GROUP BY class
HAVING AVG(CASE
WHEN sex = '男' THEN score
ELSE 0
END) < (CASE
WHEN sex = '女' THEN score
ELSE 0
END)
但是如果一个班级只有女生,且女生都是0分。这种班级就不会被查询出来。可以修改为:
/* 比较男生和女生平均分的SQL语句(1):对空集使用AVG后返回NULL */
SELECT
class
FROM
TestResults
GROUP BY class
HAVING AVG(CASE
WHEN sex = '男' THEN score
ELSE NULL
END) < (CASE
WHEN sex = '女' THEN score
ELSE NULL
END)
本节小结
调查集合性质的常用条件及其用途
NO | 条件表达式 | 用途 |
---|---|---|
1 | COUNT(DISTINCT col)=COUNT(col) | col列没有重复的值 |
2 | COUNT(*)=COUNT(col) | col列不存在NULL |
3 | COUNT(*)= MAX(col) | col列是连续的编号(起始值是1) |
4 | COUNT(*)=MAX(col)-MIN(col)+1 | col列是连续的编号(起始值是任意整数) |
5 | MIN(col)=MAX(col) | col列都是相同值,或者是NULL |
6 | MIN(col)*MAX(col)>0 | col列全是正数或全是负数 |
7 | MIN(col)*MAX(col)<0 | col列最大值是正数,最小值是负数 |
8 | MIN(ABS(col))=0 | col列最少有一个是0 |
9 | MIN(col-常量)=-MAX(col-常量) | col列的最大值和最小值与指定常量等距 |
本节要点:
01 SQL中指定搜索条件时,要搞清楚查询的实体是集合还是集合的元素
- 实体对于一行元素—>那么是元素,使用WHERE子句
- 实体对于多行元素—>那么是集合,使用HAVING子句
02 HAVING 子句可以通过聚合函数(特别是极值函数)针对集合指定各种条件
03 复杂的条件都可以通过CASE表达式生成特征函数