SQL 為聚合結果指定條件(HAVING)


學習重點

  • 使用 COUNT 函數等對表中數據進行匯總操作時,為其指定條件的不是 WHERE 子句,而是 HAVING 子句。

  • 聚合函數可以在 SELECT 子句、HAVING 子句和 ORDER BY 子句中使用。

  • HAVING 子句要寫在 GROUP BY 子句之后。

  • WHERE 子句用來指定數據行的條件,HAVING 子句用來指定分組的條件。

一、HAVING 子句

使用前一節學過的 GROUP BY 子句,可以得到將表分組后的結果。在此,我們來思考一下通過指定條件來選取特定組的方法。例如,如何才能取出“聚合結果正好為 2 行的組”呢(圖 8)?

取出符合指定條件的組

圖 8 取出符合指定條件的組

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

因此,對集合指定條件就需要使用其他的子句了,此時便可以用 HAVING 子句 [1]

KEYWORD

  • HAVING 子句

HAVING 子句的語法如下所示。

語法 3 HAVING 子句

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

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

▶ 使用 HAVING 子句時 SELECT 語句的順序

SELECTFROMWHEREGROUP BYHAVING

法則 13

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

接下來就讓我們練習一下 HAVING 子句吧。例如,針對按照商品種類進行分組后的結果,指定“包含的數據行數為 2 行”這一條件的 SELECT 語句,請參見代碼清單 20。

代碼清單 20 從按照商品種類進行分組后的結果中,取出“包含的數據行數為2行”的組

SELECT product_type, COUNT(*)
  FROM Product
 GROUP BY product_type
HAVING COUNT(*) = 2;

執行結果

 product_type | count
--------------+------
 衣服         |     2
 辦公用品     |     2

我們可以看到執行結果中並沒有包含數據行數為 4 行的“廚房用具”。未使用 HAVING 子句時的執行結果中包含“廚房用具”,但是通過設置 HAVING 子句的條件,就可以選取出只包含 2 行數據的組了(代碼清單 21)。

代碼清單 21 不使用 HAVING 子句的情況

SELECT product_type, COUNT(*)
  FROM Product
 GROUP BY product_type;

執行結果

不使用 HAVING 子句的情況

下面我們再來看一個使用 HAVING 子句的例子。這次我們還是按照商品種類對表進行分組,但是條件變成了“銷售單價的平均值大於等於 2500 日元”。

首先來看一下不使用 HAVING 子句的情況,請參見代碼清單 22。

代碼清單 22 不使用 HAVING 子句的情況

SELECT product_type, AVG(sale_price)
  FROM Product
 GROUP BY product_type;

執行結果

 product_type |         avg
--------------+----------------------
 衣服         | 2500.0000000000000000
 辦公用品     |  300.0000000000000000
 廚房用具     | 2795.0000000000000000

按照商品種類進行切分的 3 組數據都顯示出來了。下面我們使用 HAVING 子句來設定條件,請參見代碼清單 23。

代碼清單 23 使用 HAVING 子句設定條件的情況

SELECT product_type, AVG(sale_price)
  FROM Product
 GROUP BY product_type
HAVING AVG(sale_price) >= 2500;

執行結果

 product_type |          avg
--------------+----------------------
 衣服         | 2500.0000000000000000
 廚房用具     | 2795.0000000000000000

銷售單價的平均值為 300 日元的“辦公用品”在結果中消失了。

二、HAVING 子句的構成要素

HAVING 子句和包含 GROUP BY 子句時的 SELECT 子句一樣,能夠使用的要素有一定的限制,限制內容也是完全相同的。HAVING 子句中能夠使用的 3 種要素如下所示。

  • 常數

  • 聚合函數

  • GROUP BY 子句中指定的列名(即聚合鍵)

代碼清單 20 中的例文指定了 HAVING COUNT(*)= 2 這樣的條件,其中 COUNT(*) 是聚合函數,2 是常數,全都滿足上述要求。反之,如果寫成了下面這個樣子就會發生錯誤(代碼清單 24)。

代碼清單 24 HAVING 子句的不正確使用方法

SELECT product_type, COUNT(*)
  FROM Product
 GROUP BY product_type
HAVING product_name = '圓珠筆';

執行結果

ERROR: 列"product,product_name"必須包含在GROUP BY子句當中,或者必須在聚合函數中使用
行 4: HAVING product_name = '圓珠筆';

product_name 列並不包含在 GROUP BY 子句之中,因此不允許寫在 HAVING 子句里。在思考 HAVING 子句的使用方法時,把一次匯總后的結果(類似表 2 的表)作為 HAVING 子句起始點的話更容易理解。

表 2 按照商品種類分組后的結果

product_type COUNT(*)
廚房用具 4
衣服 2
辦公用品 2

可以把這種情況想象為使用 GROUP BY 子句時的 SELECT 子句。匯總之后得到的表中並不存在 product_name 這個列,SQL 當然無法為表中不存在的列設定條件了。

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

也許有的讀者已經發現了,有些條件既可以寫在 HAVING 子句當中,又可以寫在 WHERE 子句當中。這些條件就是聚合鍵所對應的條件。原表中作為聚合鍵的列也可以在 HAVING 子句中使用。因此,代碼清單 25 中的 SELECT 語句也是正確的。

代碼清單 25 將條件書寫在 HAVING 子句中的情況

SELECT product_type, COUNT(*)
  FROM Product
 GROUP BY product_type
HAVING product_type = '衣服';

執行結果

 product_type | count
--------------+------
 衣服         |     2

上述 SELECT 語句的返回結果與代碼清單 26 中 SELECT 語句的返回結果是相同的。

代碼清單 26 將條件書寫在 WHERE 子句中的情況

SELECT product_type, COUNT(*)
  FROM Product
WHERE product_type = '衣服'
 GROUP BY product_type;

執行結果

 product_type | count
--------------+------
 衣服         |     2

雖然條件分別寫在 WHERE 子句和 HAVING 子句當中,但是條件的內容以及返回的結果都完全相同。因此,大家可能會覺得兩種書寫方式都沒問題。

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

理由有兩個。

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

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

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

其次,對初學者來說,研究 DBMS 的內部實現這一話題有些深奧,這里就不做介紹了,感興趣的讀者可以參考隨后的專欄——WHERE 子句和 HAVING 子句的執行速度。

法則 14

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

專欄

WHERE 子句和 HAVING 子句的執行速度

WHERE 子句和 HAVING 子句中都可以使用的條件,最好寫在 WHERE 子句中的另一個理由與性能即執行速度有關系。由於性能不在本教程介紹的范圍之內,因此暫不進行說明。通常情況下,為了得到相同的結果,將條件寫在 WHERE 子句中要比寫在 HAVING 子句中的處理速度更快,返回結果所需的時間更短。

為了理解其中原因,就要從 DBMS 的內部運行機制來考慮。使用 COUNT 函數等對表中的數據進行聚合操作時,DBMS 內部就會進行排序處理。排序處理是會大大增加機器負擔的高負荷的處理[2]。因此,只有盡可能減少排序的行數,才能提高處理速度。

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

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

KEYWORD

  • 索引(index)

請參閱

(完)


  1. HAVING 是 HAVE( 擁有 )的現在分詞,並不是通常使用的英語單詞。 ↩︎

  2. 雖然 Oracle 等數據庫會使用散列(hash)處理來代替排序,但那同樣也是加重機器負擔的處理。 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM