Self Join 自連接 (SQL Self Join)

Self Join (自連接) 是指一個資料表與自己進行 JOIN 操作。當資料表中的某些記錄與同一資料表中的其他記錄有關聯時,就會用到這種技巧。

Self Join 並不是一種特殊的 JOIN 類型,而是一種使用方式。你可以使用任何 JOIN 類型 (INNER JOINLEFT JOINRIGHT JOIN 等) 來實現自連接。

💡 重要: 在進行 Self Join 時,必須為同一個資料表設定不同的別名 (Alias),以區分「左邊的表」和「右邊的表」。

Self Join 語法 (Syntax)

使用隱式語法:

SELECT a.column1, b.column2
FROM table_name a, table_name b
WHERE a.column = b.column;

使用標準 JOIN 語法 (推薦):

SELECT a.column1, b.column2
FROM table_name a
JOIN table_name b ON a.column = b.column;

注意這裡 ab 都是同一個 table_name 的別名。

範例 1:查詢員工與主管關係

這是 Self Join 最經典的應用場景。假設有一個 employees 資料表,其中 manager_id 欄位儲存該員工主管的 employee_id

employees 資料表:

employee_idnamemanager_id
1王總經理NULL
2李經理1
3張工程師2
4陳工程師2

範例: 查詢每位員工及其主管的名字。

SELECT
    e.name AS 員工,
    m.name AS 主管
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;

結果:

員工主管
王總經理NULL
李經理王總經理
張工程師李經理
陳工程師李經理

說明:

  • 這裡使用 LEFT JOIN 是為了讓沒有主管的員工 (如王總經理) 也能出現在結果中。
  • e 代表「員工」,m 代表「主管」,兩者都是 employees 表的別名。

範例 2:查詢同城市的客戶配對

customers 資料表:

customer_idnamecity
1張一台北
2王二台北
3李三高雄
4陳四台北

範例: 找出住在同一城市的客戶配對。

SELECT
    a.name AS 客戶A,
    b.name AS 客戶B,
    a.city AS 城市
FROM customers a
JOIN customers b ON a.city = b.city
WHERE a.customer_id < b.customer_id;

結果:

客戶A客戶B城市
張一王二台北
張一陳四台北
王二陳四台北

說明:

  • WHERE a.customer_id < b.customer_id 這個條件有兩個作用:
    1. 避免自己與自己配對 (張一-張一)
    2. 避免重複配對 (張一-王二 和 王二-張一 只保留一組)

範例 3:查詢價格相同的不同產品

products 資料表:

product_idproduct_nameprice
1滑鼠500
2鍵盤800
3網路攝影機500
4耳機800

範例: 找出價格相同的不同產品。

SELECT
    a.product_name AS 產品A,
    b.product_name AS 產品B,
    a.price AS 價格
FROM products a
JOIN products b ON a.price = b.price
WHERE a.product_id < b.product_id;

結果:

產品A產品B價格
滑鼠網路攝影機500
鍵盤耳機800

常見的 Self Join 使用情境

情境說明
階層式資料員工-主管、分類-父分類、組織架構
配對查詢找出同城市/同部門/同價格的記錄
資料比較比較同一表中不同記錄的差異
序列資料比較前後記錄 (如計算成長率)

範例 4:計算連續日期的銷售差異

daily_sales 資料表:

sale_dateamount
2024-01-011000
2024-01-021200
2024-01-03950

範例: 計算每天與前一天的銷售差異。

SELECT
    today.sale_date AS 日期,
    today.amount AS 當日銷售額,
    yesterday.amount AS 前日銷售額,
    today.amount - yesterday.amount AS 差異
FROM daily_sales today
LEFT JOIN daily_sales yesterday
    ON today.sale_date = DATE_ADD(yesterday.sale_date, INTERVAL 1 DAY);

💡 提示: 對於這類序列資料的分析,現代 SQL 的視窗函數 (如 LAG()) 通常是更簡潔高效的解決方案。

Self Join 的效能考量

Self Join 實際上是將同一個表讀取兩次,因此:

  1. 確保有適當的索引:連接欄位應該建立索引以提高查詢效率。
  2. 注意資料量:大型資料表的 Self Join 可能會很慢。
  3. 考慮替代方案:某些情況下,視窗函數子查詢CTE 可能是更好的選擇。