查詢反模式 - GroupBy、HAVING的理解

爲了最簡單地說明問題,我特意設計了一張這樣的表。數據庫

  

1、GROUP BY單值規則

  規則1:單值規則,跟在SELECT後面的列表,對於每一個分組來講,必須返回且僅僅返回一個值。函數

  典型的表現就是跟在SELECT後面的列,若是沒有使用聚合函數,必須出如今GROUP BY子句後面。性能

  以下面這個查詢報錯:設計

  

  由於對於按照部門分組以後,技術部分組有3個編號,銷售部分組有2個編號,你讓數據庫顯示哪一個呢?3d

  若是假設你使用聚合函數COUNT(編號)以後,對於每一個部門分組,就只有一個值 - 該部門下的人數:code

  

  下面來實戰下,咱們但願查詢出每一個部門,最高工資的那我的的姓名,部門,工資。blog

  

  Shit,出師不利。第一次實戰就錯誤了,咱們來分析下。it

  很明顯,上面的姓名列是不符合單值規則的。咱們的一廂情願想法是,MAX(工資)以後,SQL Server就能自動幫咱們返回不符合單值規則的'姓名'。可是很遺憾,SQL Server並無這麼作。理由以下:class

  1.   若是兩我的的工資相同,那麼應該將哪一個人的姓名返回?
  2.   若是咱們使用的不是MAX()聚合函數,而是SUM、AVG等聚合函數(沒有與之匹配的工資),那麼姓名返回哪一個?
  3.   若是在查詢語句中使用了兩個聚合函數,如MAX(),MIN()。那麼應該返回的是MAX工資的姓名,仍是MIN工資的姓名呢?

  綜上所述,數據庫是不可能可以根據咱們輸入的一個聚合函數,就幫助咱們判斷並顯示出不符合單值規則的列的。變量

  對於MYSQL來講,當有這種不符合單值規則的列時,默認是返回這一組結果的第一條記錄。而SQLite是返回最後一條。

  所以,對於以上查詢,咱們要另尋解決方案。

  解決方案1:關聯子查詢

SELECT 姓名,部門,工資 FROM 工資表 AS T1
WHERE NOT EXISTS (SELECT NULL FROM 工資表 AS T2 WHERE T1.部門 = T2.部門 AND T2.工資 > T1.工資)

  輸出以下:

  

  徹底符合要求。對於上面的關聯子查詢,能夠理解爲:

  遍歷工資表的全部記錄,查找不存在比當前記錄部門相同且工資還大的記錄。

  雖然,關聯子查詢的語法很是簡單,可是性能並很差。由於對於每一條記錄,都要執行一次子查詢。

  解決方案2:衍生表

   使用衍生表的思路是,先執行一個子查詢,獲得一個臨時結果集,而後用臨時結果集和原表進行INNER JOIN操做。就能獲得最高工資的人的信息。

  

  剛寫出這個SQL語句時,以爲很是妙,理解了以後以爲很是妙。

SELECT 姓名,T1.部門,工資 FROM 工資表 AS T1 INNER JOIN
(
    SELECT 部門,MAX(工資) AS 最高 FROM 工資表    --執行查詢,先記錄兩個字段 部門-最高工資
    GROUP BY 部門
) AS T2        --衍生表T2
ON T1.部門 = T2.部門 AND 工資 = 最高

  衍生表的方式性能優於關聯子查詢,由於衍生表的方式只執行了一次子查詢。可是它須要一張臨時表來存儲臨時記錄。所以,這個方案也並非最佳的解決方案。

  解決方案3:使用JOIN + IS NULL

  這是一個更妙的解決方案,當咱們用一個外聯結去匹配記錄時,當匹配的記錄不存在,就會用NULL來代替相應的列。

  咱們先來看一條很是簡答的SQL語句:

  

  從中你看到了什麼?當T2表中,不存在比T1表中工資高的記錄時就返回NULL。

  那麼,那麼,那麼一個IS NULL是否是就解決問題了呢?

  

  好妙,好妙的方法,讓人拍案叫絕的使用了OUTER JOIN。

  JOIN解決方案適用於針對大量數據查詢而且可伸縮比較時。它老是能比基於子查詢的解決方案更好地適應數據量的變量。

  解決方案4:對額外的列使用聚合函數

  咱們知道,GROUP BY時,SELECT列表必須返回的是單值,那麼咱們可不能夠經過使用聚合函數,讓這個列返回單值呢?答案是能夠的。

  

  其實,返回的數據是有問題的,當工資相同時,它就返回按姓名從大到小排列的第一個姓名。也就是說,當工資相同時,它只可以返回一條記錄。

  咱們將聚合函數換成MIN看看。

  

  解決方案5:Row_Number() + OVER

  WITH B AS
  (
      SELECT row_number() OVER(PARTITION BY Name ORDER BY CreateTime) AS part ,Score, Name, CreateTime
      FROM xxx
  )
  SELECT * FROM B WHERE Part = 1

 

  輸出以下:

  

2、HAVING的理解

  WHERE與HAVING的區別:

  •   WHERE(分組前過濾):WHERE不能對聚合函數列進行過濾,由於執行WHERE的時候,分組還沒有執行,聚合函數也未執行。
  •   HAVING(分組後過濾):主要用於對聚合函數列進行過濾,由於HAVING實在分組以後執行的。HAVING子句只能配合GROUP BY子句使用。沒有GROUP BY子句時不能使用HAVING。

  錯誤使用WHERE的示例:

  

  正確使用WHERE與HAVING的示例:

  

相關文章
相關標籤/搜索