SQL学习—HAVING子句又回来了

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表达式生成特征函数

猜你喜欢

转载自blog.csdn.net/weixin_43387060/article/details/86567769