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

MySQL GROUP BY 非聚合欄位特性:一對多關聯查詢取任意單條記錄的實戰技巧

     編輯:MySQL 綜合教學

MySQL 允許 SELECT 列表包含未出現在 GROUP BY 且非聚合函數的欄位(非 ONLY_FULL_GROUP_BY 模式下),這為「一對多關聯查詢每組取任意一條」提供了極簡寫法。本文詳細講解這一特性機制、適用場景、跨資料庫差異與生產環境風險控制。

場景:供應商-商品一對多,每供應商取任意一商品

表結構


CREATE TABLE t_supplier (
  id INT PRIMARY KEY,
  name VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE t_goods (
  id INT PRIMARY KEY AUTO_INCREMENT,
  supplier_id INT NOT NULL,
  name VARCHAR(50),
  FOREIGN KEY (supplier_id) REFERENCES t_supplier(id)
) ENGINE=InnoDB;

-- 測試資料
INSERT INTO t_supplier VALUES (1,'天津供應商'),(2,'北京供應商');
INSERT INTO t_goods VALUES 
  (1,1,'天津產品1'),(2,1,'天津產品2'),
  (3,2,'北京產品1'),(4,2,'北京產品2');

MySQL 極簡寫法


SELECT * 
FROM t_supplier s
LEFT JOIN t_goods g ON g.supplier_id = s.id
GROUP BY s.id;

結果(非確定性)


id | name         | id | supplier_id | name
---|--------------|----|-------------|-------------
1  | 天津供應商   | 1  | 1           | 天津產品1   -- 或 天津產品2
2  | 北京供應商   | 3  | 2           | 北京產品1   -- 或 北京產品2

機制深度解析

為什麼能這樣寫?

  • MySQL 預設 sql_mode 不包含 ONLY_FULL_GROUP_BY (5.7.5 前預設關閉,5.7.5+ 預設開啟)
  • 優化器對每組任意選取一行的非聚合欄位值(通常是物理存儲順序第一行)
  • 這是 MySQL 擴展語法,**非 SQL 標準**,Oracle/PostgreSQL/SQL Server 均報錯

驗證當前模式


SELECT @@sql_mode;
-- 若包含 ONLY_FULL_GROUP_BY,上述查詢將報錯:
-- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause...

生產環境風險與最佳實踐

⚠️ 風險點

  1. 結果非確定性:同樣查詢不同時刻可能返回不同商品(資料重組、索引變更、平行查詢均可能改變選取行)
  2. 版本升級失效:MySQL 5.7.5+ 預設開啟 ONLY_FULL_GROUP_BY,直接導致語法報錯
  3. 遷移不兼容:換用 PostgreSQL、Oracle、TiDB 等嚴格模式資料庫時必須改寫

✅ 標準化改寫方案(跨資料庫通用)

方案一:ROW_NUMBER() 窗口函數 (MySQL 8.0+, PG, Oracle, SQL Server)


WITH ranked AS (
  SELECT s.*, g.*,
         ROW_NUMBER() OVER (PARTITION BY s.id ORDER BY g.id) AS rn
  FROM t_supplier s
  LEFT JOIN t_goods g ON g.supplier_id = s.id
)
SELECT * FROM ranked WHERE rn = 1;

方案二:關聯子查詢取首條 (通用、效能尚可)


SELECT s.*, 
       (SELECT g.name FROM t_goods g WHERE g.supplier_id = s.id ORDER BY g.id LIMIT 1) AS goods_name
FROM t_supplier s;

方案三:JOIN 衍生表 (MySQL 5.6+ 支援)


SELECT s.*, g.*
FROM t_supplier s
LEFT JOIN (
  SELECT supplier_id, MIN(id) AS first_goods_id
  FROM t_goods
  GROUP BY supplier_id
) fg ON fg.supplier_id = s.id
LEFT JOIN t_goods g ON g.id = fg.first_goods_id;

方案四:用戶變數模擬行號 (MySQL 5.7 及以下)


SELECT s.*, g.name
FROM t_supplier s
LEFT JOIN (
  SELECT g.*, 
         @rn := IF(@prev = supplier_id, @rn + 1, 1) AS rn,
         @prev := supplier_id
  FROM t_goods g
  CROSS JOIN (SELECT @rn := 0, @prev := 0) vars
  ORDER BY supplier_id, id
) g ON g.supplier_id = s.id AND g.rn = 1;

何時可安全使用 MySQL 非標準 GROUP BY?

  • 臨時分析、一次性報表、內部工具、結果不要求確定性
  • 確認環境 sql_mode 不含 ONLY_FULL_GROUP_BY 且短期不升級
  • 明確註釋:-- 非標準寫法,結果非確定性,僅供臨時分析

結語

MySQL 的非標準 GROUP BY 是把「雙刃劍」:極簡寫法換取結果非確定性與遷移風險。現代應用**強烈建議使用窗口函數或標準化改寫**,確保業務邏輯穩定、可遷移、可審計。僅在臨時探索性分析中作為便捷捷徑使用。

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