3 SQL 聚合與排序

yinliangyun發表於2018-10-18

 

3 聚合與排序

3-1 對錶進行聚合查詢

聚合函式

通過SQL對資料進行 操作或計算時需要使用函式。

計算表中全部資料行數時,可以使用COUNT函式。

 

COUNT : 計算表中的記錄數(行數)。

SUM : 計算表中數值列的資料合計值。

AVG : 計算表中數值列的資料平均值。

MAX :計算表中任意列中資料的最大值。

MIN :計算表中任意列中資料的最小值。

 

如上所示,用於合計的函式稱為聚合函式或者集合函式。本書中統稱為聚合函式。所謂聚合,就是將多行彙總為一行。

 

函式這個詞,與我們在學校課上學到的意思是一樣的,就像是輸入某個值就能輸出相應結果的盒子一樣。

 

計算表中資料的行數

/*

COUNT  計算表中的記錄數(行數)

sum  計算表中數值列的資料合計值

avg  計算表中數值列的資料平均值

max  求出表中任意列中資料的最大值

min  求出表中任意列中資料的最小值

*/

 

— 此處的輸入值稱為引數或者parameter,輸出值稱為返回值

 

SELECT COUNT(*) FROM shohin;

 

SELECT COUNT(shiire_tanka) FROM shohin;

 

即使對同一張表使用COUNT函式,輸入的引數不同得到的結果也會不同。

星號對於COUNT函式來說是獨有的,其他函式並不能將星號作為引數(如果使用星號會出錯)。

 

計算NULL以外資料的行數

SELECT COUNT(shiire_tanka) FROM shohin;

 

對於COUNT函式來說,引數列不同計算的結果也會發生變化。

法則3-1

COUNT函式的結果根據引數的不同而不同,COUNT(*) 會得到包含NULL的資料行數,而COUNT(<列名>)會得到NULL之外的資料行數。

 

計算合計值

SELECT SUM(hanbai_tanka) FROM shohin;

SELECT SUM(shiire_tanka) FROM shohin;

 

法則3-2

聚合函式會將NULL排除在外。但COUNT(*)例外,並不會排除NULL。

 

計算平均值

— (值的合計)/(值的個數)就是平均值的計算公式     如果遇到空的情況會先剔除null 值的合計和值的個數都需要去除。

SELECT AVG(hanbai_tanka), AVG(shiire_tanka) FROM shohin;

 

計算最大值和最小值

SELECT MAX(hanbai_tanka), MIN(shiire_tanka) FROM shohin;

 

SELECT MAX(torokubi), MIN(torokubi) FROM shohin;

 

法則3-3

MAX/MIN函式幾乎適用於所有資料型別的列。SUM/AVG函式只適用於數值型別的列。

 

使用聚合函式刪除重複值(關鍵字DISTINCT)

SELECT COUNT(DISTINCT shohin_bunrui) FROM shohin;

— 請注意,這時DISTINCT必須寫在括號中。這是因為必須要在計算行數之前刪除shohin_bunrui列中的重複資料。

SELECT SUM(hanbai_tanka), SUM(DISTINCT hanbai_tanka) FROM shohin;

SELECT DISTINCT COUNT(shohin_bunrui) FROM shohin;

 

— DISTINCT不僅限於COUNT函式,所有的聚合函式都可以使用。

 

法則3-4

想要計算值的種類時,可以在COUNT函式的引數中使用DISTINCT。

 

法則3-5

在聚合函式的引數中使用DISTINCT,可以刪除重複資料。

 

3-2 對錶進行分組

 

目前為止,我們看到的聚合函式的使用方法,無論是否包含NULL,無論是否刪除了重複資料,都是針對表中的所有資料進行的聚合處理。下面,我們先把表分成幾組,然後再進行聚合處理。

 

GROUP BY子句

/*

SELECT <列名1>, <列名2>, …… FROM <表名> GROUP BY <列名1>, <列名2>, ……;

*/

SELECT shohin_bunrui, COUNT(*) FROM shohin;

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui;

這樣,GROUP BY子句就像切蛋糕那樣將表進行分組。在GROUP BY子句中指定的列稱為聚合鍵或者分組列。

當然,GROUP BY子句也和SELECT子句一樣,可以通過逗號分隔指定多列。

法則3-6

GROUP BY就像是切分表的一把刀。

 

法則3-7

SQL子句的順序不能改變,也不能互換位置。

 

子句的書寫順序(暫定) :SELECT > FROM > WHERE > GROUP BY

聚合鍵中包含NULL的情況

SELECT shiire_tanka, COUNT(*) FROM shohin GROUP BY shiire_tanka;

— 當聚合鍵中包含null時,也會將null作為一組特定的資料    這裡的null 大家可以理解為“不確定”

SELECT shiire_tanka, COUNT(*) FROM shohin GROUP BY shiire_tanka;

 

從結果我們可以看出,當聚合鍵中包含NULL時,也會將NULL作為一組特定的資料。這裡的NULL,大家可以理解為“不確定”。

 

法則3-8

聚合鍵中包含NULL時,在結果中會以“不確定”行(空行)的形式表現出來。

 

使用WHERE子句時GROUP BY的執行結果

/*

SELECT <列名1>, <列名2>, …… FROM <表名> WHERE … GROUP BY <列名1>, <列名2>, ……;

*/

 

SELECT shiire_tanka, COUNT(*) FROM shohin WHERE shohin_bunrui = `衣服`;

 

SELECT shiire_tanka, COUNT(*) FROM shohin WHERE shohin_bunrui = `衣服` GROUP BY shiire_tanka;

 

像這樣使用WHERE子句進行聚合處理時,會先根據WHERE子句指定的過濾條件進行過濾,然後再進行聚合處理。

 

GROUP BY和WHERE並用時SELECT語句的執行順序 :FROM > WHERE > GROUP BY > SELECT

與聚合函式和GROUP BY子句有關的常見錯誤

— 其他dbms可能報錯

SELECT shohin_mei, shiire_tanka, COUNT(*) FROM shohin GROUP BY shiire_tanka;

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui;

SELECT shohin_bunrui AS sb, COUNT(*) FROM shohin GROUP BY sb;

 

SELECT shohin_bunrui, COUNT(*) FROM shohin WHERE COUNT(2) GROUP BY shohin_bunrui;

 

法則3-9

使用GROUP BY子句時,SELECT子句中不能出現聚合鍵之外的列名。

 

法則3-10

在GROUP BY子句中不能使用SELECT子句中定義的別名。

 

法則3-11

GROUP BY子句結果的顯示是無序的。

 

法則3-12

只有SELECT子句和HAVING子句(以及ORDER BY子句)中能夠使用聚合函式。

 

DISTINCT和GROUP BY

— DISTINCT和GROUP BY能夠實現相同的功能

SELECT DISTINCT shohin_bunrui FROM shohin;

SELECT shohin_bunrui FROM shohin GROUP BY shohin_bunrui;

— 除次之外,它們還都會把NULL作為一個獨立的結果返回,對多列使用時也會得到完全相同的結果。其實不僅處理結果相同,執行速度也基本差不多,到底應該使用哪一個呢?

但其實這個問題本身就是本末倒置的,我們應該考慮的是該SELECT語句是否滿足需求。選擇的標準其實非常簡單,在“想要刪除選擇結果中的重複記錄”時使用DISTINCT,在“想要計算聚合結果”時使用GROUP BY。

不適用COUNT等聚合函式,而只使用GROUP BY子句的SELECT語句,會讓人覺得非常奇怪。難免使人產生“到底為什麼要對錶進行分組呢?這樣做有必要嗎?”等疑問。

 

 

3-3 為聚合結果指定條件

HVING子句

/*

SELECT <列名1>, <列名2>, …… FROM <表名> GROUP BY <列名1>, <列名2>, …… HAVING <分組結果對應的條件>;

*/

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui;

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui HAVING COUNT(*) = 2;

SELECT shohin_bunrui, AVG(hanbai_tanka) FROM shohin GROUP BY shohin_bunrui;

SELECT shohin_bunrui, AVG(hanbai_tanka) FROM shohin GROUP BY shohin_bunrui HAVING AVG(hanbai_tanka) >= 2500;

 

說到指定條件,估計大家都會首先想到WHERE子句。但是,WHERE子句只能指定記錄(行)的條件,而不能用來指定組的條件(例如,“資料行數為2行”或者“平均值為500等”)。

 

HAVING子句必須寫在GROUP BY子句之後。其在DBMS內部的執行順序也排在GROUP BY子句之後。

 

使用HAVING子句時SELECT語句的順序 :SELECT > FROM > WHERE > GROUP BY > HAVING

 

法則3-13

HAVING子句要寫在GROUP BY子句之後。

 

HAVING子句的構成要素

— HAVING子句中能夠使用的3種要素:常數/聚合函式/GROUP BY子句中指定的列名(即聚合鍵)

 

SELECT shohin_mei, shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_mei HAVING shohin_mei = `圓珠筆`;

 

相當於HAVING子句,更適合寫在WHERE子句中的條件

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui HAVING shohin_bunrui = `衣服`;

 

SELECT shohin_bunrui, COUNT(*) FROM shohin WHERE shohin_bunrui = `衣服` GROUP BY shohin_bunrui;

 

也許有的讀者已經發現了,有些條件既可以寫在HAVING子句當中,有可以寫在WHERE子句當中。這些條件就是聚合鍵所對應的條件。

 

如果僅從結果來看的話,確實如此。但筆者卻認為,聚合鍵所對應的條件還是應該書寫在WHERE子句之中。

 

根本原因是where子句和having子句的作用不同,如前所述,having子句是用來指定“組”的條件的,因此,“行”所對應的條件還是應該寫在where子句當中,這樣一來,書寫出的select語句不但可以分清兩者各自的功能,理解起來也更容易

 

Where子句 = 指定行所對應的條件

Having子句 = 指定組所對應的條件

其次where執行速度快一點

 

通常情況下,為了得到相同的結果,將條件寫在WHERE子句中要比寫在HAVING子句中的處理速度更快,返回結果所需時間更短。

為了理解其中原因,就要從DBMS的內部執行機制來考慮。使用COUNT函式等對錶中的資料進行聚合操作時,DBMS內部就會進行排序處理。排序處理會大大增加機器的負擔,此即所謂高負荷的處理。因此,只有儘可能減少排序的行數,才能增加處理速度。

通過WHERE子句指定條件時,由於排序之前就對資料進行了過濾,所以能夠減少排序的資料量。但HAVING子句是在排序之後才對資料進行分組的,因此與在WHERE子句中指定條件比起來,需要排序的資料量就會多得多。雖然DBMS的內部處理不盡相同,但是對於排序處理來說,基本上都是一樣的。

此外,WHERE子句更具速度優勢的另一個理由是,可以對WHERE子句指定條件所對應的列建立索引,這樣也可以大幅度提高處理速度。建立索引是一種非常普遍的提高DBMS效能的方法,效果也十分明顯,這對WHERE子句來說也十分有利。

法則3-14

聚合鍵所對應的條件不應該書寫在HAVING子句當中,而應該書寫在WHERE子句當中。

 

3-4 對查詢結果進行排序

通常,從表中抽取資料時,如果沒有特別指定順序,最終排列順序便無從得知。即使是同一條SELECT語句,每次執行時排列順序很可能發生改變。

但是不進行排序,很可能出現結果混亂的情況。這時,便需要通過在SELECT語句末尾新增ORDER BY子句來明確指定排列順序。

 

ORDER BY子句

— SELECT <列名1>, <列名2>, …… FROM <表名> ORDER BY <排序基準列1>, <排序基準列2>, …;

 

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin;

 

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY hanbai_tanka;

 

不論何種情況,ORDER BY子句都需要寫在SELECT語句的末尾。這是因為對資料行進行排序的操作必須在結果即將返回時執行。ORDER BY子句中書寫的列名稱為排序鍵。

 

子句的書寫順序 :SELECT > FROM > WHERE > GROUP BY > HAVING > ORDER BY

 

法則3-15

ORDER BY子句通常寫在SELECT語句的末尾。

 

指定升序(ASC)和降序(DESC)

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY hanbai_tanka DESC;

 

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY hanbai_tanka ASC;

 

由於ASC和DESC這兩個關鍵字是以列為單位指定的,所以可以同時指定一個列為升序,指定其他列為降序。

法則3-16

未指定ORDER BY子句中排列順序時會預設使用升序進行排列。

 

指定多個排序鍵

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY hanbai_tanka, shohin_id;

 

這樣一來,就可以在ORDER BY子句中同時指定多個排序鍵了。會優先使用左側的鍵,如果該列存在相同值的話,會接著參考右側的鍵。當然,也可以同時使用3個以上的排序鍵。

NULL的順序

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY shiire_tanka;

 

不能對NULL使用比較運算子,也就是說,不能對NULL和數字進行排序。也不能與字串和日期比較大小。因此,使用含有NULL的列作為排序鍵時,NULL會在結果的開頭或末尾彙總顯示。

究竟是在開頭顯示還是在末尾顯示,並沒有特殊規定。某些DBMS中可以指定NULL在開頭或末尾顯示,希望大家對自己使用的DBMS的功能研究以下。

 

法則3-17

排序鍵中包含NULL時,會在開頭或末尾進行彙總。

 

在排序鍵中使用顯示用別名

SELECT shohin_id AS id, shohin_mei, hanbai_tanka AS ht, shiire_tanka FROM shohin ORDER BY ht, id;

 

不能在GROUP BY子句中使用的別名,為什麼可以在ORDER BY子句中使用呢?這是因為SQL語句在DBMS內部的執行順序被掩蓋起來了。

使用HAVING子句時SELECT語句執行順序 :FROM > WHERE >GROUP BY > HAVING > SELECT > ORDER BY

這只是一個粗略的總結,雖然具體的執行順序根據DBMS的不同而不同,但是大家有這樣一個大致的印象就可以了。一定要記住SELECT子句的執行順序在GROUP BY子句之後,ORDER BY子句之前。因此,在執行GROUP BY子句時,SELECT語句中定義的別名無法被識別。對於在SELECT子句之後執行的ORDER BY子句來說,就沒有這樣的問題了。

 

法則3-18

在ORDER BY子句中可以使用SELECT子句中定義的別名。

 

ORDER BY 子句中可以使用存在於表中,但並不包含在SELECT子句中的列。除此之外,還可以使用聚合函式。

ORDER BY子句中可以使用的列

SELECT shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY shohin_id;

 

SELECT shohin_bunrui, COUNT(*) FROM shohin GROUP BY shohin_bunrui ORDER BY COUNT(*);

 

法則3-19

在ORDER BY子句中可以使用SELECT子句中未使用的列和聚合函式。

 

不要使用列編號

— ORDER BY 子句中可以使用列的編號,列編號是指SELECT子句中的列按照從左到右的順序進行排列時所對應的編號(1,2,3,…)。  不建議使用編號  程式碼閱讀起來比較難  該排序功能將來會被刪除

 

— 使用列名

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY hanbai_tanka DESC, shohin_id;

 

— 使用列編號

SELECT shohin_id, shohin_mei, hanbai_tanka, shiire_tanka FROM shohin ORDER BY 3 DESC, 1;

 

雖然列編號使用起來非常方便,但我們並不推薦使用,原因有以下兩點 :第一,程式碼閱讀起來比較難;第二,這也是根本問題。實際上,在SQL-92中已經明確指出該排序功能將來會被刪除。

 

法則3-20

在ORDER BY子句中不要使用列編號。

相關文章