首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

MySQL 無 GROUP BY 直接 HAVING 返回空之謎:MIN/MAX 與 GROUP BY NULL 隱式行為深度解析

     編輯:MySQL 資料庫相關知識

現象重現

CREATE TABLE t2 (a INT, id INT PRIMARY KEY AUTO_INCREMENT);
INSERT INTO t2 VALUES (1,1), (1,3);

-- 有結果
SELECT * FROM t2 HAVING id = MIN(id);  -- 返回 id=1

-- 無結果 (空集)
SELECT * FROM t2 HAVING id = MAX(id);  -- 空集!

修改數據 `UPDATE t2 SET a=10 WHERE id=1;` 後:

SELECT * FROM t2 HAVING a = MAX(a);  -- 返回 a=10
SELECT * FROM t2 HAVING a = MIN(a);  -- 空集!

**規律**:`HAVING col = MIN(col)` 有結果,`HAVING col = MAX(col)` 無結果 (反之亦然,取決於第一行值)。

根因深度解析

1. MySQL 非標準重寫:隱式 `GROUP BY NULL`

標準 SQL 要求 `HAVING` 配合 `GROUP BY`。MySQL 為兼容單表 `HAVING`,內部重寫為:

SELECT * FROM t HAVING id=MIN(id)
-- 重寫為
SELECT * FROM t GROUP BY NULL HAVING id=MIN(id)

2. `GROUP BY NULL` 等價於 `LIMIT 1` (取第一行)

SELECT * FROM t2 GROUP BY NULL;
-- 只返回第一行數據 (a=10, id=1)

**關鍵點**:分組後**只有一組**,且該組**只包含第一行數據**。

3. 聚合函數 `MIN/MAX` 計算範圍是「全表」而非「分組內」

SELECT MAX(a), MIN(a), MAX(id), MIN(id) FROM t2 GROUP BY NULL;
-- 結果:MAX(a)=10, MIN(a)=1, MAX(id)=3, MIN(id)=1
-- 證明:聚合函數掃描全表所有行,不受 GROUP BY NULL 限制!

4. 邏輯推演

  • 原查詢:`SELECT * FROM t2 HAVING id = MAX(id)`
  • 重寫後:`SELECT * FROM t2 GROUP BY NULL HAVING id = MAX(id)`
  • 執行邏輯:
    1. 全表掃描計算 `MAX(id) = 3`。
    2. `GROUP BY NULL` 產生一個分組,**僅包含第一行 (id=1)**。
    3. 對該分組唯一行 (id=1) 判斷 `HAVING` 條件:`1 = 3` → **False**。
    4. 結果集為空。
  • 對比 `HAVING id = MIN(id)`:`MIN(id)=1`,第一行 `id=1`,條件 `1=1` → **True**,返回第一行。

5. 對比:標準 `GROUP BY a` 行為

SELECT MAX(a), MIN(a) FROM t2 GROUP BY a;
-- a=1 組: MAX=1, MIN=1
-- a=10 組: MAX=10, MIN=10
-- 每組內聚合函數僅計算組內數據,結果一致,HAVING 正常工作

核心結論

無 `GROUP BY` 的 `HAVING` 在 MySQL 中會被重寫為 `GROUP BY NULL`,導致聚合函數 (MIN/MAX/SUM/COUNT) 在全表範圍計算,而 HAVING 判斷卻只在「第一行」組成的單一分組上進行。這種「全表聚合值 vs 單行數據」的錯位,是結果反直覺的根本原因。

最佳實踐:拒絕花式 SQL,寫標準語法

錯誤寫法標準寫法 (推薦)說明
`SELECT * FROM t HAVING id=MIN(id)``SELECT * FROM t WHERE id = (SELECT MIN(id) FROM t)`子查詢語義清晰、標準、跨數據庫兼容
`SELECT * FROM t HAVING id=MAX(id)``SELECT * FROM t WHERE id = (SELECT MAX(id) FROM t)`同上
`SELECT * FROM t HAVING a=MAX(a)``SELECT * FROM t WHERE a = (SELECT MAX(a) FROM t)`同上

延伸:MySQL 8.0+ `ONLY_FULL_GROUP_BY` 模式

開啟 `sql_mode=ONLY_FULL_GROUP_BY` (默認開啟) 後,`SELECT * FROM t HAVING id=MIN(id)` 將**直接報錯**:

ERROR 1140 (42000): In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 't.id'; this is incompatible with sql_mode=only_full_group_by

這強制開發者編寫標準 SQL,避免隱式行為陷阱。

總結

  • **現象**:`HAVING col=MIN(col)` 有結果,`HAVING col=MAX(col)` 無結果 (視第一行值而定)。
  • **本質**:MySQL 重寫為 `GROUP BY NULL` → 聚合函數全表計算,HAVING 單行判斷 → 錯位。
  • **教訓**:**永遠使用標準 SQL** (`WHERE col = (SELECT AGG(col) FROM t)`)。依賴非標準隱式行為是技術債。
  • **配置**:生產環境**強制開啟 `ONLY_FULL_GROUP_BY`**,在編譯/測試階段攔截非標準 SQL。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved