Self Join 自連接 (SQL Self Join)
Self Join (自連接) 是指一個資料表與自己進行 JOIN 操作。當資料表中的某些記錄與同一資料表中的其他記錄有關聯時,就會用到這種技巧。
Self Join 並不是一種特殊的 JOIN 類型,而是一種使用方式。你可以使用任何 JOIN 類型 (INNER JOIN、LEFT JOIN、RIGHT 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;
注意這裡 a 和 b 都是同一個 table_name 的別名。
範例 1:查詢員工與主管關係
這是 Self Join 最經典的應用場景。假設有一個 employees 資料表,其中 manager_id 欄位儲存該員工主管的 employee_id:
employees 資料表:
| employee_id | name | manager_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_id | name | city |
|---|---|---|
| 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這個條件有兩個作用:- 避免自己與自己配對 (張一-張一)
- 避免重複配對 (張一-王二 和 王二-張一 只保留一組)
範例 3:查詢價格相同的不同產品
products 資料表:
| product_id | product_name | price |
|---|---|---|
| 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_date | amount |
|---|---|
| 2024-01-01 | 1000 |
| 2024-01-02 | 1200 |
| 2024-01-03 | 950 |
範例: 計算每天與前一天的銷售差異。
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 實際上是將同一個表讀取兩次,因此: