標準 SQL 要求 SELECT 列表中所有非聚合欄位必須出現在 GROUP BY 中。但 MySQL 預設啟用 ONLY_FULL_GROUP_BY 關閉時,允許此類查詢並任意返回分組內第一行的值(非確定性)。
-- 建表與測試資料
CREATE TABLE t (id INT, name VARCHAR(100), aa VARCHAR(45));
INSERT INTO t VALUES
(1,'aaaa','bbbb'), (1,'1111','2222'), (1,'2222','33333'),
(1,'2222','44444'), (2,'2222','44444'), (2,'2222','1111'),
(3,'2222','1111'), (1,'2222','44444'), (1,'2222','44444'),
(1,'2222','44444'), (3,'2222','aaaa');
-- 僅 GROUP BY id,但 SELECT 包含 aa
SELECT id, COUNT(1), aa FROM t GROUP BY id;
結果:aa 顯示各組第一行的值(如 id=1 顯示 'bbbb'),COUNT(1) 正確。此行為依賴儲存引擎與索引順序,不具備可移植性,生產環境嚴禁依賴。
MySQL 5.7+ 預設開啟 ONLY_FULL_GROUP_BY,強制標準行為。正確寫法:
-- 方案 1:所有非聚合欄位加入 GROUP BY
SELECT id, aa, COUNT(1) FROM t GROUP BY id, aa;
-- 方案 2:使用聚合函數明確語義
SELECT id, ANY_VALUE(aa), COUNT(1) FROM t GROUP BY id;
HAVING 用於過濾分組後的聚合結果,WHERE 無法使用聚合函數。
-- 查詢總分 > 1000 的學生
SELECT student, SUM(score) AS total
FROM scores
GROUP BY student
HAVING total > 1000;
-- 結合 WHERE(行過濾)與 HAVING(組過濾)
SELECT dept, AVG(salary) AS avg_sal
FROM employees
WHERE hire_date > '2020-01-01'
GROUP BY dept
HAVING avg_sal > 50000;
| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
SELECT name, SUM(pt) FROM t GROUP BY name HAVING pt > 100 | pt 非聚合且不在 GROUP BY | HAVING SUM(pt) > 100 |
SELECT id, name FROM t GROUP BY id | 非確定性結果 | 加入 name 或用 ANY_VALUE(name) |
用 HAVING 替代 WHERE 做非聚合過濾 | 效率低,語義不清 | 行級過濾用 WHERE |
ONLY_FULL_GROUP_BY,編寫標準相容 SQL。GROUP BY 定義分組維度,HAVING 過濾聚合結果,兩者分工明確。