1. 程式人生 > >《sql進階教程》之HAVING子句

《sql進階教程》之HAVING子句

本文是《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 子句可以通過聚合函式(特別是極值函式)針對集合指定各種條件