首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

MySQL 無 GROUP BY 直接 HAVING 返回空行問題深度分析 MySQL 無 GROUP BY 直接 HAVING 返回空行問題深度分析

     編輯:MySQL 綜合教學

現象重現

有一表 t2(id PK, a),數據:(1,10), (3,1)。

-- 返回 1 行
SELECT * FROM t2 HAVING id = MIN(id);  -- id=1

-- 返回空集
SELECT * FROM t2 HAVING id = MAX(id);  -- 預期 id=3,實際空集

-- 同理 a 列
SELECT * FROM t2 HAVING a = MAX(a);  -- 返回 a=10 (id=1)
SELECT * FROM t2 HAVING a = MIN(a);  -- 返回空集

根因解析

1. MySQL 重寫規則

單獨使用 HAVING 不符合 SQL 標準,MySQL 內部將其重寫為:

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

2. GROUP BY NULL 的行為

SELECT * FROM t2 GROUP BY NULL;
-- 等價於 LIMIT 1,只返回第一行數據
+------+----+
| a    | id |
+------+----+
| 10   | 1  |
+------+----+

3. 聚合函數計算範圍

關鍵點:MIN/MAX 等聚合函數計算的是全表所有行,而非 GROUP BY 後的單組數據。

SELECT MAX(a), MIN(a), MAX(id), MIN(id) FROM t2 GROUP BY NULL;
+--------+--------+---------+---------+
| MAX(a) | MIN(a) | MAX(id) | MIN(id) |
+--------+--------+---------+---------+
| 10     | 1      | 3       | 1       |
+--------+--------+---------+---------+

4. 邏輯推演

  • HAVING id = MIN(id) → HAVING id = 1 → 第一行 id=1 滿足 → 返回 1 行
  • HAVING id = MAX(id) → HAVING id = 3 → 第一行 id=1 不滿足 → 返回空集
  • HAVING a = MAX(a) → HAVING a = 10 → 第一行 a=10 滿足 → 返回 1 行
  • HAVING a = MIN(a) → HAVING a = 1 → 第一行 a=10 不滿足 → 返回空集

正確用法對比

-- 加上 GROUP BY 列,聚合函數在組內計算,結果一致
SELECT MAX(a), MIN(a), MAX(id), MIN(id) FROM t2 GROUP BY a;
+--------+--------+---------+---------+
| MAX(a) | MIN(a) | MAX(id) | MIN(id) |
+--------+--------+---------+---------+
| 1      | 1      | 3       | 3       |
| 10     | 10     | 1       | 1       |
+--------+--------+---------+---------+

核心結論

⚠️ 無 GROUP BY 的 HAVING 是反模式

  • MySQL 為兼容性允許,但語義極易誤解
  • GROUP BY NULL 等價 LIMIT 1,取第一行
  • 聚合函數 (MIN/MAX/SUM/AVG) 計算全表範圍,非組內
  • 結果取決於「第一行數據」與「全表聚合值」的巧合匹配

最佳實踐

  1. 永遠配合 GROUP BY 使用 HAVING,明確分組邏輯
  2. 若需「取最小/最大 ID 的行」,用標準寫法:
    -- 取最小 id 的行
    SELECT * FROM t2 ORDER BY id ASC LIMIT 1;
    
    -- 取最大 id 的行
    SELECT * FROM t2 ORDER BY id DESC LIMIT 1;
  3. 若需「每組取最值」,用窗口函數 (8.0+) 或子查詢:
    -- 8.0+ 窗口函數
    SELECT * FROM (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY a ORDER BY id) AS rn
        FROM t2
    ) t WHERE rn = 1;
  4. 代碼審查時標記無 GROUP BY 的 HAVING 為代碼異味

總結

「無 GROUP BY 直接 HAVING」看似能跑通,實則是「第一行數據值」與「全表聚合值」巧合相等的副作用。理解 GROUP BY NULL ≡ LIMIT 1 及聚合函數計算範圍,即可避開此坑。寫 SQL 時請遵循標準語法:WHERE 過濾行 → GROUP BY 分組 → HAVING 過濾組。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved