在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值相關聯;
數據庫中的函數依賴表示兩個屬性集之間的限制關係。
參考:
2.What does Functional Dependency mean?