LEFT JOIN 是以左表為基準、右表匹配的連接方式。當右表關聯字段存在重複值(1:N 關係)時,結果集行數會超過左表,導致報表重複、統計失真。本文通過「會員-登錄日誌」實戰案例,演示如何用子查詢聚合保證右表唯一性,獲得預期的 1:1 或 1:0 結果。
SELECT A.*, B.col FROM A LEFT JOIN B ON A.id = B.a_id; -- 左表 A 全量返回,右表 B 匹配不到則 NULL
-- member 表(左表) +----+----------+ | id | username | +----+----------+ | 1 | Alice | | 2 | Bob | +----+----------+ -- member_login_log 表(右表,同一用戶多條登錄記錄) +----+-----+------------+ | id | uid | logindate | +----+-----+------------+ | 1 | 1 | 2024-01-10 | | 2 | 1 | 2024-01-15 | | 3 | 2 | 2024-01-12 | +----+-----+------------+
SELECT a.id, a.username, b.logindate FROM member a LEFT JOIN member_login_log b ON a.id = b.uid; -- 結果:Alice 出現兩行,Bob 一行,共 3 行(預期 2 行) +----+----------+------------+ | id | username | logindate | +----+----------+------------+ | 1 | Alice | 2024-01-10 | | 1 | Alice | 2024-01-15 | ← 重複行 | 2 | Bob | 2024-01-12 | +----+----------+------------+
業務需求:查詢每位會員的最後登錄日期。
SELECT a.id, a.username, b.last_login
FROM member a
LEFT JOIN (
SELECT uid, MAX(logindate) AS last_login
FROM member_login_log
GROUP BY uid
) b ON a.id = b.uid;
-- 結果:完美 1:1/1:0,共 2 行
+----+----------+------------+
| id | username | last_login |
+----+----------+------------+
| 1 | Alice | 2024-01-15 |
| 2 | Bob | 2024-01-12 |
+----+----------+------------+
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 子查詢 + GROUP BY 聚合 | 需取最新/最舊/聚合值 | 邏輯清晰、標準 SQL、優化器友好 | 大表聚合可能慢,需適當索引 |
| ROW_NUMBER() 窗口函數 (MySQL 8.0+) | 需保留完整行信息 | 一次掃描、可取任意列 | 版本要求高 |
| 關聯子查詢 SELECT (SELECT MAX...) | 簡單標量子查詢 | 寫法簡潔 | 大表性能差,優化器難下推 |
| 應用層去重 | 極簡查詢、結果集小 | SQL 簡單 | 網絡傳輸浪費、不推薦 |
(uid, logindate) 建立聯合索引,支援 GROUP BY uid 且 MAX(logindate) 可走索引掃描。id 為主鍵,天然最優。若業務頻繁出現「左表 1:N 右表」且需 JOIN,說明表設計可能未達到 3NF:
member.last_login)以避免 JOIN?記住:LEFT JOIN 兩表最好保持 1:1 或 1:0 關係,若右表不唯一,必須在 JOIN 前通過子查詢聚合/去重,否則結果語義模糊、性能難控。