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');
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
sql_mode 不包含 ONLY_FULL_GROUP_BY (5.7.5 前預設關閉,5.7.5+ 預設開啟)
SELECT @@sql_mode;
-- 若包含 ONLY_FULL_GROUP_BY,上述查詢將報錯:
-- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause...
ONLY_FULL_GROUP_BY,直接導致語法報錯
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;
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;
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;
sql_mode 不含 ONLY_FULL_GROUP_BY 且短期不升級-- 非標準寫法,結果非確定性,僅供臨時分析MySQL 的非標準 GROUP BY 是把「雙刃劍」:極簡寫法換取結果非確定性與遷移風險。現代應用**強烈建議使用窗口函數或標準化改寫**,確保業務邏輯穩定、可遷移、可審計。僅在臨時探索性分析中作為便捷捷徑使用。