MySQL 聚合函數(三)MySQL對GROUP BY的處理


  原文來自MySQL 5.7 官方手冊:12.20.3 MySQL Handling of GROUP BY

 

  SQL-92和更早版本不允許SELECT列表,HAVING條件或ORDER BY列表引用未在GROUP BY子句中命名的非聚合列的查詢。即以下查詢是被禁止的:

SELECT o.custid, c.name, MAX(o.payment)
FROM orders AS o, customers AS c
WHERE o.custid = c.custid
GROUP BY o.custid;

  SQL-1999以及更高版本允許將這種查詢作為一個可選項,前提是這些列在功能上依賴GROUP BY列(if they are functionally dependent on GROUP BY columns)——如果name和custid之間存在這種關系,則查詢是合法的,例如custid是customer的一個主鍵。

  MySQL 5.7.5及更高版本實現了對功能依賴的檢測。如果啟用了ONLY_FULL_GROUP_BY SQL模式(默認情況下是這樣),MySQL會拒絕在Select列別、Having條件或者ORDER BY列表中有引用既未在GROUP BY子句中命名也未在功能上依賴於它們的非聚合列。

  在5.7.5之前,MySQL不檢測功能依賴性,默認情況下不啟用ONLY_FULL_GROUP_BY。(難怪,我的是5.7.21,默認不開啟。)

  那當不啟用ONLY_FULL_GROUP_BY時,MySQL就不得不接受前面這種查詢。在這種情況下,服務器可以自由選擇每個組中的任何值,因此除非它們相同,否則所選的值是不確定的,這可能不是您想要的。

  此外,添加ORDER BY子句不會影響每個組中值的選擇。結果集的排序發生在值被選擇之后,所以ORDER BY並不會影響服務器如何選擇每個組中的值。

  當你知道,由於數據的某些屬性,每個未在GROUP BY中命名的非聚合列中的所有值對於每個組都是相同的。此時禁止ONLY_FULL_GROUP_BY可能是有用的。

 

  以下的討論展示功能性依賴、以及當功能性依賴缺失時MySQL產生的錯誤信息,以及讓MySQL在功能性依賴缺失時接受查詢的方式。

  在ONLY_FULL_GROUP_BY模式下,下面的查詢可能是非法的:

SELECT name, address, MAX(age) FROM t GROUP BY name;

  但,如果name是t的一個主鍵,又或者name是一個unique、NOT NULL字段,這個查詢會變成合法的。在這種情況下,MySQL會識別出查詢列address功能性依賴與group列。例如,若name是一個主鍵,則其值確定address的值,因為每個組只有一個主鍵值,因此只有一行。因此,MySQL對組中address值的選擇並不會有隨機性,也不需要拒絕查詢。

  反過來,如果name是並不是t的一個主鍵,又或者name也不是一個unique、NOT NULL字段,這個查詢就是非法的了,因為在這種情況下,MySQL不能推斷出功能依賴性並發生錯誤:

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP
BY clause and contains nonaggregated column 'mydb.t.address' which
is not functionally dependent on columns in GROUP BY clause; this
is incompatible with sql_mode=only_full_group_by

  那如果非要MySQL接受這個查詢,就可以使用ANY_VALUE()函數:

SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;

  當然,也可以放大招,禁止ONLY_FULL_GROUP_BY模式。

  然而,這里的例子非常簡單。 特別是,我們不太可能在單個主鍵列上進行分組,因為每個組只包含一行。其它對於在更復雜的查詢中演示“功能性依賴”的示例,參考12.20.4。

 

  如果一個select查詢中包含了聚合函數,卻沒有GROUP BY子句。那么在ONLY_FULL_GROUP_BY模式下,它不能在select子句的列表中、HAVING條件中、ORDER BY列表中包含非聚合列。如下所示:

/*sql_mode=ONLY_FULL_GROUP_BY*/
mysql> SELECT name, MAX(age) FROM t;
ERROR 1140 (42000): In aggregated query without GROUP BY, expression
#1 of SELECT list contains nonaggregated column 'mydb.t.name'; this
is incompatible with sql_mode=only_full_group_by

  不存在Group By子句時,就只存在一個組,同時也不確定為這個組選擇哪個name值。這種情況下,如果MySQL選擇的name值是無關緊要的,ANY_VALUE()就可以派上用場了:

/*不會報錯*/
SELECT ANY_VALUE(name), MAX(age) FROM t;

 

  在MySQL 5.7.5及更高版本中,ONLY_FULL_GROUP_BY也會影響使用了DISTINCT和ORDER BY的查詢。

  假設具有三列c1,c2和c3的表t,其中包含以下行:

/*

c1 c2 c3
1  2  A
3  4  B
1  2  C

*/

  假設我們執行以下查詢,期望結果按c3列進行排序:

SELECT DISTINCT c1, c2 FROM t ORDER BY c3;

  為了對結果排序,必須先刪除重復項。但是要這樣做,我們應該保留第一行還是第三行?這種任意選擇會影響c3的保留值,而反過來c3的保留值又會影響排序,使得排序也任意了。

  為了防止這個問題,如果任何ORDER BY表達式不滿足以下條件中的至少一個,則有DISTINCT和ORDER BY的查詢將被拒絕為無效:

  • 表達式與select列表中的某個相等;
  • 所有被該表達式引用、並且屬於查詢所選表的列,都是select列表中國的元素

 

  MySQL相對於標准SQL的另一個擴展是:允許在Having子句中引用在SELECT從句中命名的別名。

  例如,以下查詢返回name值出現一次的行:

SELECT name, COUNT(name) FROM orders
GROUP BY name
HAVING COUNT(name) = 1;

  但是MySQL擴展后可以如下使用:

SELECT name, COUNT(name) AS c FROM orders
GROUP BY name
HAVING c = 1;

NOTE:在MySQL 5.7.5之前,啟用ONLY_FULL_GROUP_BY會禁用此擴展,因此需要使用非別名表達式來編寫HAVING子句。

  按前面的我的筆記,Having子句是在Select子句前被執行的,看起來似乎是錯的?試驗了一下,在我的版本(5.7.21)中,這樣做沒問題(猜想一下,和編譯順序相關?):

select SID,count(SId) as n from sc group by SId having n=3;

/*

+------+---+
| SID  | n |
+------+---+
| 01   | 3 |
| 02   | 3 |
| 03   | 3 |
| 04   | 3 |
+------+---+

*/

 

  還可以提一下,標准SQL在GROUP BY子句中僅允許有列表達式(column expressions),因此諸如此類的語句無效,因為FLOOR(value / 100)是非列表達式( noncolumn expression):

SELECT id, FLOOR(value/100)
  FROM tbl_name
  GROUP BY id, FLOOR(value/100);

  而MySQL對此進行了擴展,上述語句有效。

  標准SQL也不允許GROUP BY子句中出現別名,MySQL則允許。所以上述查詢也可以更改為:

SELECT id, FLOOR(value/100) AS val
  FROM tbl_name
  GROUP BY id, val;

  這個val被視為列表達式。

 

  當GROUP BY中出現非列表達式時,MySQL會識別該表達式與Select子句列表中的表達式之間的相等性。這意味着啟用了ONLY_FULL_GROUP_BY SQL模式后,包含GROUP BY id,FLOOR(value/100)的查詢是有效的,因為Select列表中出現了相同的FLOOR()表達式。

  但是,MySQL不會嘗試識別GROUP BY非列表達式的功能依賴(functional dependence),因此以下查詢在啟用ONLY_FULL_GROUP_BY時無效,即使Select列表中的第三個表達式是一個關於id列的簡單公式:id與GROUP BY中的FLOOR()相加。(即id+FLOOR(value/100)與GROUP BY的列不存在功能依賴)

SELECT id, FLOOR(value/100), id+FLOOR(value/100)
  FROM tbl_name
  GROUP BY id, FLOOR(value/100);

  解決方法是使用派生表:

SELECT id, F, id+F
  FROM
    (SELECT id, FLOOR(value/100) AS F
     FROM tbl_name
     GROUP BY id, FLOOR(value/100)) AS dt;

 


免責聲明!

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



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