《sql进阶教程》之HAVING子句

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31156277/article/details/84847520

本文是《sql进阶教程》阅读笔记,感兴趣可以阅读该书对应章节,这本适合有一定sql基础的同学阅读。另外作者《sql基础教程》也值得一看

案例一、各队,全体点名

查出现在可以出勤的队伍。可以出勤即队伍里所有队员都处于“待命”状态
Teams

member(队员) team_id(队伍编号 ID) status(状态)
1 待命
1 出勤中
米克 1 待命
卡伦 2 出勤中
凯斯 2 休息
3 待命
哈特 3 待命
迪克 3 待命
贝斯 4 待命
阿伦 5 出勤中
罗伯特 5 休息
卡根 5 待命
-- 用谓词表达全称量化命题
SELECT team_id, member
FROM Teams T1
WHERE NOT EXISTS
(SELECT *
	FROM Teams T2
	WHERE T1.team_id = T2.team_id
	AND status <> '待命' 
);
-- “所有队员都处于待命状态”=“不存在不处于待命状态的队员”


-- 用集合表达全称量化命题(1)
SELECT team_id
FROM Teams
GROUP BY team_id
HAVING COUNT(*) = SUM(CASE WHEN status = '待命' THEN 1 ELSE 0 END);

--如果元素最大值和最小值相等,那么这个集合中肯定只有一种值
SELECT team_id
FROM Teams
GROUP BY team_id
HAVING MAX(status) = '待命'
AND MIN(status) = '待命';

-- 列表显示各个队伍是否所有队员都在待命
SELECT team_id, 
CASE WHEN MAX(status) = '待命' AND MIN(status) = '待命'
THEN '全都在待命'
ELSE '队长!人手不够' END AS status
FROM Teams
GROUP BY team_id;

二、单重集合与多重集合

允许循环插入和频繁读写的表中有可能产生重复数据。在定义表时加入唯一性约束可以预防表中产生重复数据,但是有些情况下根据具体的业务需求不同,产生重复数据也是合理的

有下面这样一张管理各个生产地的材料库存的表
Materials

center(生产地) receive_date(入库日期) material(材料)
东京 2007-4-01
东京 2007-4-12
东京 2007-5-17
东京 2007-5-20
大阪 2007-4-20
大阪 2007-4-22
大阪 2007-4-29
名古屋 2007-3-15
名古屋 2007-4-01
名古屋 2007-4-24
名古屋 2007-4-24
名古屋 2007-5-02
名古屋 2007-5-10
福冈 2007-5-10
福冈 2007-5-28
-- 选中材料存在重复的生产地
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 划分子集

-- 存在重复的集合:使用EXISTS
--过将 HAVING 改写成 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 改写后的 SQL 语句也能够查出重复的具体是哪一种材料;
--如果想要查出不存在重复材料的生产地有哪些,只需要把 EXISTS 改写为 NOT EXISTS 就可以了。


案例三、寻找缺失的编号:升级版

-- 如果有查询结果,说明存在缺失的编号
SELECT '存在缺失的编号' AS gap
FROM SeqTbl
HAVING COUNT(*) <> MAX(seq);

-- 如果有查询结果,说明存在缺失的编号:只调查数列的连续性
SELECT '存在缺失的编号' AS gap
FROM SeqTbl
HAVING COUNT(*) <> MAX(seq) - MIN(seq) + 1 ;

-- 不论是否存在缺失的编号都返回一行结果
SELECT CASE WHEN COUNT(*) = 0 THEN '表为空'
WHEN COUNT(*) <> MAX(seq) - MIN(seq) + 1 THEN '存在缺失的编号'
ELSE '连续' END AS gap
FROM SeqTbl;

案例四、为集合设置详细的条件

记录了学生考试成绩的表为例进行讲解
TestResults

student_id(学号 ID) class(班级) sex(性别) score(分数)
001 A 100
002 A 100
003 A 49
004 A 30
005 B 100
006 B 92
007 B 80
008 B 80
009 B 10
010 C 92
011 C 80
012 C 21
013 D 100
014 D 0
015 D 0

问题一、请查询出 75% 以上的学生分数都在 80 分以上的班级

SELECT class
FROM TestResults
GROUP BY class
HAVING COUNT(*) * 0.75 <= SUM(CASE WHEN score >= 80 THEN 1 ELSE 0 END) ;

问题二、请查询出分数在 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);

问题三、请查询出女生平均分比男生平均分高的班级

-- 比较男生和女生平均分的SQL 语句(1):对空集使用AVG 后返回0
SELECT class
FROM TestResults
GROUP BY class
HAVING AVG(CASE WHEN sex = '男' THEN score ELSE 0 END)
< AVG(CASE WHEN sex = '女' THEN score ELSE 0 END) ;

注意:根据标准 SQL 的定义,对空集使用 AVG 函数时,结果会返回 NULL


小结

概括使用 HAVING 子句时的要点,就是要搞清楚将什么东西抽象成集合

集合性质的常用条件及其用途

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 列的最大值和最小值与指定常量等距

一、如果使用 CASE 表达式来生成特征函数,那么无论多么复杂且通用的条件,我们都可以描述
二、HAVING 子句可以通过聚合函数(特别是极值函数)针对集合指定各种条件

猜你喜欢

转载自blog.csdn.net/qq_31156277/article/details/84847520