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

MySQL LEFT JOIN 右表數據不唯一導致結果膨脹?GROUP BY 子查詢優雅解決

     編輯:MySQL 資料庫相關知識

LEFT JOIN 是多表關聯查詢利器,但當右表匹配行不唯一(1:N)時,結果集行數會超過左表,導致業務邏輯錯誤。本文通過「新聞分類」(1:1)與「會員登入日誌」(1:N)兩個對比案例,演示問題現象與 GROUP BY 子查詢預聚合解決方案。

一、LEFT JOIN 基礎語義

SELECT a.*, b.col FROM table_a a LEFT JOIN table_b b ON a.id = b.a_id;
  • 左表(A)全量輸出
  • 右表(B)僅輸出匹配行,無匹配則 NULL 填充
  • 理想關係:1:1 或 1:0

二、案例 1:完美 1:1 關聯(新聞-分類)

SELECT n.id, n.title, c.name AS category_name
FROM news n LEFT JOIN news_category c ON n.category_id = c.id;

結果:每條新聞對應唯一分類,無匹配則 category_name 為 NULL ✅

news 表結構

news_category 表結構

查詢結果

三、案例 2:1:N 導致結果膨脹(會員-登入日誌)

表結構:member(id, username) 1:N member_login_log(uid, logindate)

member 表

member_login_log 表

直接 LEFT JOIN(錯誤示範)

SELECT m.id, m.username, l.logindate
FROM member m LEFT JOIN member_login_log l ON m.id = l.uid;

結果:某會員有 3 條日誌 → 輸出 3 行,會員信息重複 ❌

膨脹結果

正確方案:子查詢預聚合保證 1:1

SELECT m.id, m.username, l.last_login
FROM member m
LEFT JOIN (
    SELECT uid, MAX(logindate) AS last_login
    FROM member_login_log
    GROUP BY uid
) l ON m.id = l.uid;

結果:每會員僅 1 行,last_login 為最近登入時間 ✅

正確結果

四、聚合策略選擇指南

業務需求聚合函數適用場景
最近一次MAX(date) / MIN(date)登入日誌、操作記錄
統計次數COUNT(*)訪問頻次、下單次數
最新狀態SUBSTRING_INDEX(GROUP_CONCAT(status ORDER BY time DESC), ',', 1)狀態流轉歷史
金額匯總SUM(amount)訂單金額、消費總額
去重列表GROUP_CONCAT(DISTINCT col)標簽、分類聚合

五、進階替代方案

方案 B:窗口函數

SELECT m.id, m.username, l.logindate
FROM member m
LEFT JOIN (
    SELECT uid, logindate,
           ROW_NUMBER() OVER (PARTITION BY uid ORDER BY logindate DESC) AS rn
    FROM member_login_log
) l ON m.id = l.uid AND l.rn = 1;

優勢:可保留完整行信息(不僅聚合列),MySQL 8.0+ 支援

方案 C:應用層組裝

  • 分兩次查詢:主表全量 + 關聯表按主鍵 IN 查詢
  • 應用層 Map 組裝,避免 SQL 複雜度
  • 適用於微服務、緩存友好場景

六、設計原則總結

  1. 表設計階段:評估關聯基數,1:N 關係在右表建立唯一索引或冗余聚合字段
  2. 查詢編寫:LEFT JOIN 前先確認右表唯一性,不唯一必用子查詢/窗口函數預聚合
  3. EXPLAIN 驗證:關注 rows、filtered、Using temporary、Using filesort
  4. 監控告警:慢查詢中 LEFT JOIN 產生臨時表超過閾值觸發優化
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved