典型一對多模型:users (一) ↔ orders (多)。需求:查詢「至少有一筆訂單金額 > 1000 的使用者」或「所有訂單金額皆 > 1000 的使用者」。
| 寫法 | 語義 | 結果差異 |
|---|---|---|
| JOIN + WHERE | 先關聯再過濾 | 若多表關聯產生重複行,WHERE 會過濾掉不符合的行,導致「一」端資料遺失 |
| JOIN + ON 條件 | 關聯時過濾 | 保留左表所有行,右表不符合條件顯示 NULL(LEFT JOIN) |
| EXISTS 子查詢 | 存在性判斷 | 不產生重複行,效能通常優於 JOIN |
| 聚合 + HAVING | 群組過濾 | 適合「所有/任一」邏輯 |
users: id, name orders: id, user_id, amount, status -- 測試資料 INSERT INTO users VALUES (1,'Alice'),(2,'Bob'),(3,'Cindy'); INSERT INTO orders VALUES (1,1,500,'paid'),(2,1,1500,'paid'), (3,2,800,'paid'),(4,2,1200,'paid'), (5,3,2000,'pending');
SELECT u.* FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id AND o.amount > 1000
);
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
SELECT DISTINCT u.* FROM users u JOIN orders o ON o.user_id = u.id WHERE o.amount > 1000;
SELECT u.* FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id AND o.amount <= 1000
);
SELECT u.* FROM users u JOIN orders o ON o.user_id = u.id GROUP BY u.id HAVING MIN(o.amount) > 1000; -- 或 MAX(o.amount) > 1000 視邏輯
SELECT u.*, o.id AS order_id, o.amount FROM users u LEFT JOIN orders o ON o.user_id = u.id AND o.amount > 1000;
關鍵:條件放在 ON 而非 WHERE。若放 WHERE,o.amount > 1000 會過濾掉 NULL 行,變成 INNER JOIN。
SELECT u.id, u.name,
COUNT(o.id) AS order_cnt,
COALESCE(SUM(o.amount),0) AS total_amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.amount > 1000
GROUP BY u.id, u.name;
orders(user_id, amount) 複合索引支援上述所有查詢LIMIT 使用延遲關聯或游標分頁| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
| LEFT JOIN ... WHERE o.amount > 1000 | 隱性轉為 INNER JOIN,丟失無符合訂單的使用者 | 條件移至 ON |
| JOIN 後不 GROUP BY 直接 SELECT u.name | 非聚合欄位未分組,MySQL ONLY_FULL_GROUP_BY 報錯 | 加 GROUP BY u.id, u.name |
| 子查詢返回多欄位用 IN | IN (SELECT a,b...) 語法錯誤 | 改用 EXISTS 或 ROW 比較 |
一對多篩選核心原則:存在性用 EXISTS,全稱量化用 NOT EXISTS,保留主表完整資料用 LEFT JOIN + ON 條件,統計用 LEFT JOIN + GROUP BY。正確放置條件(ON vs WHERE)是避免語義錯誤的關鍵。