第08章_聚合函式
我們上一章講到了 SQL 單行函式。實際上 SQL 函式還有一類,叫做聚合(或聚集、分組)函式,它是對一組資料進行彙總的函式,輸入的是一組資料的集合,輸出的是單個值。
1. 聚合函式介紹
- 什麼是聚合函式
聚合函式作用於一組資料,並對一組資料返回一個值。
-
聚合函式型別
- AVG()
- SUM()
- MAX()
- MIN()
- COUNT()
-
聚合函式語法
- 聚合函式不能巢狀呼叫。比如不能出現類似“AVG(SUM(欄位名稱))”形式的呼叫。
1.1 AVG和SUM函式
可以對數值型資料使用AVG 和 SUM 函式。
SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary) FROM employees WHERE job_id LIKE '%REP%';
1.2 MIN和MAX函式
可以對任意資料型別的資料使用 MIN 和 MAX 函式。
SELECT MIN(hire_date), MAX(hire_date)
FROM employees;
1.3 COUNT函式
- COUNT(*)返回表中記錄總數,適用於任意資料型別。
SELECT COUNT(*)
FROM employees
WHERE department_id = 50;
- COUNT(expr) 返回expr不為空的記錄總數。
SELECT COUNT(commission_pct) FROM employees WHERE department_id = 50;
-
問題:用count(*),count(1),count(列名)誰好呢?
其實,對於MyISAM引擎的表是沒有區別的。這種引擎內部有一計數器在維護著行數。
Innodb引擎的表用count(*),count(1)直接讀行數,複雜度是O(n),因為innodb真的要去數一遍。但好於具體的count(列名)。
-
問題:能不能使用count(列名)替換count(*)?
不要使用 count(列名)來替代
count(*)
,count(*)
是 SQL92 定義的標準統計行數的語法,跟資料庫無關,跟 NULL 和非 NULL 無關。說明:count(*)會統計值為 NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。
2. GROUP BY
2.1 基本使用
可以使用GROUP BY子句將表中的資料分成若干組
SELECT column, group_function(column)
FROM table
[WHERE condition]
[GROUP BY group_by_expression]
[ORDER BY column];
明確:WHERE一定放在FROM後面
在SELECT列表中所有未包含在組函式中的列都應該包含在 GROUP BY子句中
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id ;
包含在 GROUP BY 子句中的列不必包含在SELECT 列表中
SELECT AVG(salary)
FROM employees
GROUP BY department_id ;
2.2 使用多個列分組
SELECT department_id dept_id, job_id, SUM(salary)
FROM employees
GROUP BY department_id, job_id ;
2.3 GROUP BY中使用WITH ROLLUP
使用WITH ROLLUP
關鍵字之後,在所有查詢出的分組記錄之後增加一條記錄,該記錄計算查詢出的所有記錄的總和,即統計記錄數量。
SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;
注意:
當使用ROLLUP時,不能同時使用ORDER BY子句進行結果排序,即ROLLUP和ORDER BY是互相排斥的。
3. HAVING
3.1 基本使用
過濾分組:HAVING子句
- 行已經被分組。
- 使用了聚合函式。
- 滿足HAVING 子句中條件的分組將被顯示。
- HAVING 不能單獨使用,必須要跟 GROUP BY 一起使用。
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary)>10000 ;
- 非法使用聚合函式 : 不能在 WHERE 子句中使用聚合函式。如下:
SELECT department_id, AVG(salary)
FROM employees
WHERE AVG(salary) > 8000
GROUP BY department_id;
3.2 WHERE和HAVING的對比
區別1:WHERE 可以直接使用表中的欄位作為篩選條件,但不能使用分組中的計算函式作為篩選條件;HAVING 必須要與 GROUP BY 配合使用,可以把分組計算的函式和分組欄位作為篩選條件。
這決定了,在需要對資料進行分組統計的時候,HAVING 可以完成 WHERE 不能完成的任務。這是因為,在查詢語法結構中,WHERE 在 GROUP BY 之前,所以無法對分組結果進行篩選。HAVING 在 GROUP BY 之後,可以使用分組欄位和分組中的計算函式,對分組的結果集進行篩選,這個功能是 WHERE 無法完成的。另外,WHERE排除的記錄不再包括在分組中。
區別2:如果需要通過連線從關聯表中獲取需要的資料,WHERE 是先篩選後連線,而 HAVING 是先連線後篩選。 這一點,就決定了在關聯查詢中,WHERE 比 HAVING 更高效。因為 WHERE 可以先篩選,用一個篩選後的較小資料集和關聯表進行連線,這樣佔用的資源比較少,執行效率也比較高。HAVING 則需要先把結果集準備好,也就是用未被篩選的資料集進行關聯,然後對這個大的資料集進行篩選,這樣佔用的資源就比較多,執行效率也較低。
小結如下:
優點 | 缺點 | |
---|---|---|
WHERE | 先篩選資料再關聯,執行效率高 | 不能使用分組中的計算函式進行篩選 |
HAVING | 可以使用分組中的計算函式 | 在最後的結果集中進行篩選,執行效率較低 |
開發中的選擇:
WHERE 和 HAVING 也不是互相排斥的,我們可以在一個查詢裡面同時使用 WHERE 和 HAVING。包含分組統計函式的條件用 HAVING,普通條件用 WHERE。這樣,我們就既利用了 WHERE 條件的高效快速,又發揮了 HAVING 可以使用包含分組統計函式的查詢條件的優點。當資料量特別大的時候,執行效率會有很大的差別。
4. SELECT的執行過程
4.1 查詢的結構
#方式1:
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的連線條件
AND 不包含組函式的過濾條件
GROUP BY ...,...
HAVING 包含組函式的過濾條件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式2:
SELECT ...,....,...
FROM ... JOIN ...
ON 多表的連線條件
JOIN ...
ON ...
WHERE 不包含組函式的過濾條件
AND/OR 不包含組函式的過濾條件
GROUP BY ...,...
HAVING 包含組函式的過濾條件
ORDER BY ... ASC/DESC
LIMIT ...,...
#其中:
#(1)from:從哪些表中篩選
#(2)on:關聯多表查詢時,去除笛卡爾積
#(3)where:從表中篩選的條件
#(4)group by:分組依據
#(5)having:在統計結果中再次篩選
#(6)order by:排序
#(7)limit:分頁
4.2 SELECT執行順序
你需要記住 SELECT 查詢時的兩個順序:
1. 關鍵字的順序是不能顛倒的:
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...
2.SELECT 語句的執行順序(在 MySQL 和 Oracle 中,SELECT 執行順序基本相同):
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的欄位 -> DISTINCT -> ORDER BY -> LIMIT
比如你寫了一個 SQL 語句,那麼它的關鍵字順序和執行順序是下面這樣的:
SELECT DISTINCT player_id, player_name, count(*) as num # 順序 5
FROM player JOIN team ON player.team_id = team.team_id # 順序 1
WHERE height > 1.80 # 順序 2
GROUP BY player.team_id # 順序 3
HAVING num > 2 # 順序 4
ORDER BY num DESC # 順序 6
LIMIT 2 # 順序 7
在 SELECT 語句執行這些步驟的時候,每個步驟都會產生一個虛擬表
,然後將這個虛擬表傳入下一個步驟中作為輸入。需要注意的是,這些步驟隱含在 SQL 的執行過程中,對於我們來說是不可見的。
4.3 SQL 的執行原理
SELECT 是先執行 FROM 這一步的。在這個階段,如果是多張表聯查,還會經歷下面的幾個步驟:
- 首先先通過 CROSS JOIN 求笛卡爾積,相當於得到虛擬表 vt(virtual table)1-1;
- 通過 ON 進行篩選,在虛擬表 vt1-1 的基礎上進行篩選,得到虛擬表 vt1-2;
- 新增外部行。如果我們使用的是左連線、右連結或者全連線,就會涉及到外部行,也就是在虛擬表 vt1-2 的基礎上增加外部行,得到虛擬表 vt1-3。
當然如果我們操作的是兩張以上的表,還會重複上面的步驟,直到所有表都被處理完為止。這個過程得到是我們的原始資料。
當我們拿到了查詢資料表的原始資料,也就是最終的虛擬表 vt1
,就可以在此基礎上再進行 WHERE 階段
。在這個階段中,會根據 vt1 表的結果進行篩選過濾,得到虛擬表 vt2
。
然後進入第三步和第四步,也就是 GROUP 和 HAVING 階段
。在這個階段中,實際上是在虛擬表 vt2 的基礎上進行分組和分組過濾,得到中間的虛擬表 vt3
和 vt4
。
當我們完成了條件篩選部分之後,就可以篩選表中提取的欄位,也就是進入到 SELECT 和 DISTINCT 階段
。
首先在 SELECT 階段會提取想要的欄位,然後在 DISTINCT 階段過濾掉重複的行,分別得到中間的虛擬表 vt5-1
和 vt5-2
。
當我們提取了想要的欄位資料之後,就可以按照指定的欄位進行排序,也就是 ORDER BY 階段
,得到虛擬表 vt6
。
最後在 vt6 的基礎上,取出指定行的記錄,也就是 LIMIT 階段
,得到最終的結果,對應的是虛擬表 vt7
。
當然我們在寫 SELECT 語句的時候,不一定存在所有的關鍵字,相應的階段就會省略。
同時因為 SQL 是一門類似英語的結構化查詢語言,所以我們在寫 SELECT 語句的時候,還要注意相應的關鍵字順序,所謂底層執行的原理,就是我們剛才講到的執行順序。