如何使用 SQL GROUP BY 分組和排序資料
本文介紹如何使用 SQL GROUP BY
子句分組資料,以便彙總表內容的子集。這涉及兩個新 SELECT
語句子句:GROUP BY
子句和 HAVING
子句。
一、資料分組
從 如何使用 SQL AVG、COUNT、MAX、MIN 和 SUM 彙總資料 中得知,使用 SQL 聚集函式可以彙總資料。這樣,我們就能夠對行進行計數,計算和與平均數,不檢索所有資料就獲得最大值和最小值。
目前為止的所有計算都是在表的所有資料或匹配特定的 WHERE
子句的資料上進行的。比如下面的例子返回供應商 DLL01
SELECT COUNT(*) AS num_prods
FROM Products
WHERE vend_id = 'DLL01';
輸出:
num_prods
-----------
4
如果要返回每個供應商提供的產品數目,該怎麼辦?或者返回只提供一項產品的供應商的產品,或者返回提供 10 個以上產品的供應商的產品,怎麼辦?
這就是分組大顯身手的時候了。使用分組可以將資料分為多個邏輯組,對每個組進行聚集計算。
二、建立分組
分組是使用 SELECT
語句的 GROUP BY
子句建立的。理解分組的最好辦法是看一個例子:
SELECT vend_id, COUNT(*) AS num_prods FROM Products GROUP BY vend_id;
輸出:
vend_id num_prods
------- ---------
BRS01 3
DLL01 4
FNG01 2
上面的 SELECT
語句指定了兩個列:vend_id
包含產品供應商的 ID
,num_prods
為計算欄位(用 COUNT(*)
函式建立)。
GROUP BY
子句指示 DBMS 按 vend_id
排序並分組資料。這就會對每個 vend_id
而不是整個表計算 num_prods
一次。
從輸出中可以看到,供應商 BRS01
有 3 個產品,供應商 DLL01
有 4 個產品,而供應商 FNG01
有 2 個產品。
因為使用了 GROUP BY
GROUP BY
子句指示 DBMS 分組資料,然後對每個組而不是整個結果集進行聚集。
在使用 GROUP BY
子句前,需要知道一些重要的規定。
-
GROUP BY
子句可以包含任意數目的列,因而可以對分組進行巢狀,更細緻地進行資料分組。 - 如果在
GROUP BY
子句中嵌套了分組,資料將在最後指定的分組上進行彙總。換句話說,在建立分組時,指定的所有列都一起計算(所以不能從個別的列取回資料)。 -
GROUP BY
子句中列出的每一列都必須是檢索列或有效的表示式(但不能是聚集函式)。如果在SELECT
中使用表示式,則必須在GROUP BY
子句中指定相同的表示式。不能使用別名。 - 大多數 SQL 實現不允許
GROUP BY
列帶有長度可變的資料型別(如文字或備註型欄位)。 - 除聚集計算語句外,
SELECT
語句中的每一列都必須在GROUP BY
子句中給出。 - 如果分組列中包含具有
NULL
值的行,則NULL
將作為一個分組返回。如果列中有多行NULL
值,它們將分為一組。 -
GROUP BY
子句必須出現在WHERE
子句之後,ORDER BY
子句之前。
提示:
ALL
子句Microsoft SQL Server 等有些 SQL 實現在
GROUP BY
中支援可選的ALL
子句。這個子句可用來返回所有分組,即使是沒有匹配行的分組也返回(在此情況下,聚集將返回NULL
)。具體的 DBMS 是否支援
ALL
,請參閱相應的文件。
注意:通過相對位置指定列
有的 SQL 實現允許根據
SELECT
列表中的位置指定GROUP BY
的列。例如,GROUP BY 2, 1
可表示按選擇的第二個列分組,然後再按第一個列分組。雖然這種速記語法很方便,但並非所有 SQL 實現都支援,並且使用它容易在編輯 SQL 語句時出錯。
三、過濾分組
除了能用 GROUP BY
分組資料外,SQL 還允許過濾分組,規定包括哪些分組,排除哪些分組。例如,你可能想要列出至少有兩個訂單的所有顧客。為此,必須基於完整的分組而不是個別的行進行過濾。
我們已經看到了 WHERE
子句的作用(如何使用 SQL WHERE 過濾返回的資料 提及)。
但是,在這個例子中 WHERE
不能完成任務,因為 WHERE
過濾指定的是行而不是分組。事實上,WHERE
沒有分組的概念。
那麼,不使用 WHERE
使用什麼呢?SQL 為此提供了另一個子句,就是 HAVING
子句。HAVING
非常類似於 WHERE
。
事實上,目前為止所學過的所有型別的 WHERE
子句都可以用 HAVING
來替代。唯一的差別是,WHERE
過濾行,而 HAVING
過濾分組。
提示:
HAVING
支援所有WHERE
操作符在 如何使用 SQL WHERE 過濾返回的資料 和 如何使用 SQL AND、OR、IN 和 NOT 過濾返回的資料 中,我們學習了
WHERE
子句的條件(包括萬用字元條件和帶多個操作符的子句)。學過的這些有關
WHERE
的所有技術和選項都適用於HAVING
。它們的句法是相同的,只是關鍵字有差別。
那麼,怎麼過濾分組呢?請看以下的例子:
SELECT cust_id, COUNT(*) AS orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;
輸出:
cust_id orders
---------- -----------
1000000001 2
這條 SELECT
語句的前三行類似於上面的語句。最後一行增加了 HAVING
子句,它過濾 COUNT(*) >= 2
(兩個以上訂單)的那些分組。
可以看到,WHERE
子句在這裡不起作用,因為過濾是基於分組聚集值,而不是特定行的值。
說明:
HAVING
和WHERE
的差別這裡有另一種理解方法,
WHERE
在資料分組前進行過濾,HAVING
在資料分組後進行過濾。這是一個重要的區別,
WHERE
排除的行不包括在分組中。這可能會改變計算值,從而影響HAVING
子句中基於這些值過濾掉的分組。
那麼,有沒有在一條語句中同時使用 WHERE
和 HAVING
子句的需要呢?
事實上,確實有。假如想進一步過濾上面的語句,使它返回過去 12 個月內具有兩個以上訂單的顧客。
為此,可增加一條 WHERE
子句,過濾出過去 12 個月內下過的訂單,然後再增加 HAVING
子句過濾出具有兩個以上訂單的分組。
為了更好地理解,來看下面的例子,它列出具有兩個以上產品且其價格大於等於 4 的供應商:
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;
輸出:
vend_id num_prods
------- -----------
BRS01 3
FNG01 2
這條語句中,第一行是使用了聚集函式的基本 SELECT
語句,很像前面的例子。
WHERE
子句過濾所有 prod_price
至少為 4
的行,然後按 vend_id
分組資料,HAVING
子句過濾計數為 2
或 2
以上的分組。
如果沒有 WHERE
子句,就會多檢索出一行(供應商 DLL01
,銷售 4
個產品,價格都在 4
以下):
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id
HAVING COUNT(*) >= 2;
輸出:
vend_id num_prods
------- -----------
BRS01 3
DLL01 4
FNG01 2
說明:使用
HAVING
和WHERE
HAVING
與WHERE
非常類似,如果不指定GROUP BY
,則大多數 DBMS 會同等對待它們。不過,你自己要能區分這一點。使用
HAVING
時應該結合GROUP BY
子句,而WHERE
子句用於標準的行級過濾。
四、分組和排序
GROUP BY
和 ORDER BY
經常完成相同的工作,但它們非常不同,理解這一點很重要。表 1 彙總了它們之間的差別。
表 1 ORDER BY
與 GROUP BY
ORDER BY | GROUP BY |
---|---|
對產生的輸出排序 | 對行分組,但輸出可能不是分組的順序 |
任意列都可以使用(甚至非選擇的列也可以使用) | 只可能使用選擇列或表示式列,而且必須使用每個選擇列表達式 |
不一定需要 | 如果與聚集函式一起使用列(或表示式),則必須使用 |
表 1 中列出的第一項差別極為重要。我們經常發現,用 GROUP BY
分組的資料確實是以分組順序輸出的。但並不總是這樣,這不是 SQL 規範所要求的。
此外,即使特定的 DBMS 總是按給出的 GROUP BY
子句排序資料,使用者也可能會要求以不同的順序排序。就因為你以某種方式分組資料(獲得特定的分組聚集值),並不表示你需要以相同的方式排序輸出。
應該提供明確的 ORDER BY
子句,即使其效果等同於 GROUP BY
子句。
提示:不要忘記
ORDER BY
一般在使用
GROUP BY
子句時,應該也給出ORDER BY
子句。這是保證資料正確排序的唯一方法。千萬不要僅依賴GROUP BY
排序資料。
為說明 GROUP BY
和 ORDER BY
的使用方法,來看一個例子。
下面的 SELECT
語句類似於前面那些例子。它檢索包含三個或更多物品的訂單號和訂購物品的數目:
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3;
輸出:
order_num items
--------- -----
20006 3
20007 5
20008 5
20009 3
要按訂購物品的數目排序輸出,需要新增 ORDER BY
子句,如下所示:
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;
輸出:
order_num items
--------- -----
20006 3
20009 3
20007 5
20008 5
在這個例子中,使用 GROUP BY
子句按訂單號(order_num
列)分組資料,以便 COUNT(*)
函式能夠返回每個訂單中的物品數目。
HAVING
子句過濾資料,使得只返回包含三個或更多物品的訂單。最後,用 ORDER BY
子句排序輸出。
五、SELECT 子句順序
下面回顧一下 SELECT
語句中子句的順序。表 2 以在 SELECT
語句中使用時必須遵循的次序,列出迄今為止所學過的子句。
表 2 SELECT 子句及其順序
子句 | 說明 | 是否必須使用 |
---|---|---|
SELECT |
要返回的列或表示式 | 是 |
FROM |
從中檢索資料的表 | 僅在從表選擇資料時使用 |
WHERE |
行級過濾 | 否 |
GROUP BY |
分組說明 | 僅在按組計算聚集時使用 |
HAVING |
組級過濾 | 否 |
ORDER BY |
輸出排序順序 | 否 |
六、小結
本文介紹瞭如何使用 GROUP BY
子句對多組資料進行彙總計算,返回每個組的結果。
我們看到了如何使用 HAVING
子句過濾特定的組,還知道了 ORDER BY
和 GROUP BY
之間以及 WHERE
和 HAVING
之間的差異。
原文連結:https://www.developerastrid.com/sql/sql-grouping-data/
(完)