以 user_id(主表)、user_profile(屬性表)、含重複/缺失數據的真實場景,通過可視化結果集對比,徹底講透三種連接的語義邊界與行數變化規律。
CREATE TABLE user_id (id DECIMAL(18));
CREATE TABLE user_profile (id DECIMAL(18), name VARCHAR(255));
INSERT INTO user_id VALUES (1),(2),(3),(4),(5),(6),(1); -- 1 重複,7 行
INSERT INTO user_profile VALUES (1,'aa'),(2,'bb'),(3,'cc'),(4,'dd'),(5,'ee'),(5,'EE'),(8,'zz'); -- 5 重複,8 不在 user_id,7 行
SELECT a.id, IFNULL(b.name, 'N/A') AS name
FROM user_id a
LEFT JOIN user_profile b ON a.id = b.id;

| id | name |
|---|---|
| 1 | aa |
| 2 | bb |
| 3 | cc |
| 4 | dd |
| 5 | ee |
| 5 | EE |
| 6 | N/A |
| 1 | aa |
規律:
邏輯鏡像 LEFT JOIN,右表 8 行全部保留,左表獨有行(6)丟棄,右表 8 無匹配 → 左側 NULL。
SELECT * FROM user_id a INNER JOIN user_profile b ON a.id = b.id;
-- 等價舊寫法
SELECT * FROM user_id a, user_profile b WHERE a.id = b.id;
-- 等價簡寫
SELECT * FROM user_id a JOIN user_profile b ON a.id = b.id;

| id | id | name |
|---|---|---|
| 1 | 1 | aa |
| 1 | 1 | aa |
| 2 | 2 | bb |
| 3 | 3 | cc |
| 4 | 4 | dd |
| 5 | 5 | ee |
| 5 | 5 | EE |
規律:
LEFT JOIN UNION RIGHT JOIN 模擬。SELECT COUNT(*)
FROM user_id
LEFT JOIN user_profile ON user_id.id = user_profile.id
WHERE user_profile.id IS NULL;
結果:1 行(id=6)。
| 業務需求 | 推薦連接 | 理由 |
|---|---|---|
| 主表全保留,屬性表可選(如訂單+用戶信息) | LEFT JOIN | 訂單不應因用戶信息缺失而消失 |
| 雙方必須同時存在(如用戶+角色權限) | INNER JOIN | 無效關系直接過濾,語義精確 |
| 屬性表全保留,主表可選(如字典表+業務引用) | RIGHT JOIN | 字典項不應因無業務引用而隱藏 |
| 需顯示雙方所有(含孤兒) | LEFT UNION RIGHT | 模擬 FULL OUTER JOIN |
JOIN 不是語法糖,是集合論在工程落地。搞清「誰為主、誰可缺、重複怎麼展開」,SQL 寫得準、跑得快、改得動。