MySQL對Goup By的處理

    在SQL-92以及更早的SQL語言規範中並不允許Select中的字段、HAVING中的條件或者Order by 中的字段使用沒有出現在GROUP BY中的非聚合列,例如,這個例子在標準的SQL-92規範中是不合法的,因爲select中使用的name列,而這個沒有參與聚合操作的列並未出現在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;

更正方法是去掉name或者把它加到group by中;

    但是根據SQL:99以及之後的SQL語言規範中的可選特性T301,如果這些列函數依賴(參考文末解釋)於GROUP BY 中的列時是允許這麼寫的:即如果列name和custid之間存在這種關係的話,上面的SQL就是合法的,比如當custid是customers表的主鍵的時候;

    MySQL實現了對函數依賴關係的探測。如果數據庫啓用了ONLY_FULL_GROUP_BY SQL模式(默認啓用),那麼select 列表,HAVING條件或者ORDER BY 列表中一旦引用了沒有進行聚合操作的列,而且這些列既沒有出現在GROUP BY 條件中並且GROUP BY中的列也和這些列沒有函數依賴關係MySQL是不會執行查詢的;

    如果禁用了ONLY_FULL_GROUP_BY,MySQL會對標準的GROUP BY功能進行擴展,它允許select中的列,HAVING中的條件或者ORDER BY 中的列引用非聚合列,甚至當這些列與GROUP BY 中的列沒有函數依賴關係時也是可以的。這樣MySQL就允許上述SQL語句的寫法了,這時數據庫在每個分組中會自由選擇這種列的值。因此,對於這個列來說除非在每個組中的值都是一樣的,否則最終的值可能不是你想要的,因爲這個值的選擇是不確定的。更進一步來說,這些值的選擇不會受ORDER BY 條件所影響,結果集的排序是在值選擇後進行的,並且ORDER BY 也不會影響數據庫對每個組中最終結果的選擇。如果你可以確定,根據數據之間的關係,未出現在GROUP BY中的每個非聚合列中的所有值對每個組來說都是相同的,這時禁用ONLY_FULL_GROUP_BY是有用的;

      你也可以不用禁用ONLY_FULL_GROUP_BY就實現這個功能,方法是使用ANY_VALUE()函數來處理非聚合列。

下面的討論論證函數依賴,當沒有函數依賴時MySQL產生的錯誤信息,以及在查詢中沒有函數依賴時讓MySQL執行這種查詢的方法;

在啓用ONLY_FULL_GROUP_BY時這個查詢可能是非法的,因爲非聚合列address出現在select列表中但是沒有出現在GROUP BY 條件中,

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

當name是t表的主鍵或者是一個非空且唯一的列時,這個查詢就是有效的。在這種情況下,MySQL會認爲address列函數依賴於用來分組的列。比如,如果name列是主鍵,那麼它的值確定了address也就確定了,因爲每個組中有且僅有一個主鍵值也就是一行數據。結果就是數據庫在選擇每個組中的address時將不會存在不確定性,數據庫也沒有必要拒絕這個查詢了

當name不是t表的主鍵或者不是非空惟一列時這個查詢就是無效的,在這種情況下,不存在函數依賴,並且會出現以下查詢錯誤:

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

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

對於一個給定的數據集,每個name的值事實上唯一確定了address的值,address實際上函數依賴於name,如果你瞭解了這一點,那麼爲了讓MySQL接受這個查詢,你可以使用ANY_VALUE()函數:

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

另一種方式就是禁用ONLY_FULL_GROUP_BY.

然而,之前的例子是很簡單的一種情況。尤其是你不太可能對一個單獨的主鍵列分組,因爲每一組只有一行數據。關於論證函數依賴的一些複雜查詢例子請參考12.20.4,”函數依賴探測”

在啓用了ONLY_FULL_GROUP_BY的情況下,如果一個查詢有聚合函數並且沒有GROUP BY 條件,那麼在這個查詢中,select列表,HAVING條件以及ORDER 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;

ONLY_FULL_GROUP_BY同樣會影響使用DISTINCT和ORDER BY的查詢。思考一下,假設有一個表t,該表有3列,c1,c2,c3,包含以下數據:

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

假設我們執行以下查詢語句,希望查詢結果按照c3排序

SELECT DISTINCT c1, c2 FROM t ORDER BY c3;

爲了對結果排序,重複數據得首先去掉。但是這麼做話,我們要保留第一行還是第三行?隨意決定保留哪個c3的值會影響到排序,而且排序也會變得不確定起來。對於有DISTINCT和ORDER BY的查詢來說,如果ORDER BY 表達式不滿足以下條件之一,就會認爲這個查詢語句是不正確的,以防止出現上述問題:

  • 出現在ORDER BY表達式中的列在select列表中也可以找到;
  • 所有出現在ORDER BY 表達式中且屬於所查詢表的列,同樣要出現在SELECT列表中

比如:

SQL1(正確)

SELECT DISTINCT category, category+1,NAME+"" FROM sf_product  F ORDER BY NAME+""

  

SQL2(正確)

SELECT DISTINCT category, category+1,NAME+"" FROM sf_product  F ORDER BY NAME+"",category  

SQL3(錯誤)

SELECT DISTINCT category+1,NAME+"" FROM sf_product  F ORDER BY NAME+"",category

  

另一個MySQL對標準SQL的擴展是它允許在HAVING條件中使用select列表中的別名。比如,下面的查詢語句,返回orders表中name只出現一次的記錄:

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

MySQL的功能擴展允許在HAVING條件中對聚合列使用別名:

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

標準SQL只允許在GROUP BY條件中使用列表達式,因此類似於這種的聲明是無效的,因爲FLOOR(value/100)是一個非列表達式:(在Oracle中則不能這麼用)

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

MySQL的對標準SQL的功能擴展允許非列表達式出現在GROUP BY 條件中,並且認爲上述SQL是正確的

標準SQL同樣不允許在GROUP BY條件中使用別名,MySQL擴展了標準SQL的功能以允許這種寫法,因此上述SQL也可以這麼寫:

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

別名val被認爲是GROUP BY 條件中的列表達式。

     在GROUP BY 條件中出現的非列表達式,MySQL會認爲和select列表中的是一樣的。就是說啓用了ONLY_FULL_GROUP_BY模式後,包含GROUP BY id, FLOOR(value/100)的查詢語句就是有效的了,因爲有FLOOR()同樣也在select表達式中。然而,MySQL不會嘗試識別GROUP BY 表達式中非列表達式中的函數依賴,因此在啓用了ONLY_FULL_GROUP_BY模式後以下查詢語句是無效的,即使第三個查詢表達式是一個作用在id上的簡單公式而已,並且FLOOR()表達式也出現在了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;

總結:

1.在啓用了ONLY_FULL_GROUP_BY模式時:

A.除非有函數依賴,否則Select、HAVING、ORDER  BY中的非聚合列必須出現在GROUP BY條件中;

B.如果沒有GROUP BY 條件,Select、HAVING、ORDER  BY中不允許出現的非聚合列;

C.有DISTINCT和ORDER BY 的查詢,ORDER BY 表達式要同時滿足以下條件:

  •    出現在ORDER BY表達式中的列在select列表中也可以找到;
  •    所有出現在ORDER BY 表達式中且屬於所查詢表的列,同樣要出現在SELECT列表中;

D.可以在GROUP BY條件中使用非列表達式

2.禁用ONLY_FULL_GROUP_BY模式時:

A.允許select中的列,HAVING中的條件或者ORDER BY 中的列引用非聚合列,甚至當這些列與GROUP BY 中的列沒有函數依賴關係時也是可以的;

B.如果不想禁用ONLY_FULL_GROUP_BY模式也實現這一功能,可以使用ANY_VALUE()函數實現;

3.MySQL允許在HAVING 和GROUP BY中使用別名;

注:

函數依賴(Functional Dependency):

 當一個屬性可以惟一地決定另一個屬性時,我們稱這兩個屬性之間存在函數依賴;如果R表示屬性X與Y之間的關係,這兩個屬性之間的函數依賴表現爲X->Y,表示Y函數依賴於X,這裏X爲行列集,Y爲因變量。每個X值都精確的與一個Y值相關聯;

  數據庫中的函數依賴表示兩個屬性集之間的限制關係。

參考:

1.MySQL 8.0 Reference Manual

2.What does Functional Dependency mean?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章