從基礎語法到大表優化,系統講解 DISTINCT 使用陷阱、隱式排序開銷、避免最高層使用 DISTINCT 的設計原則,並以「哥譚市蝙蝠車購買者」案例演示如何用 EXISTS/IN 子查詢重構消除 DISTINCT。
SELECT DISTINCT * FROM table_name; SELECT *, COUNT(DISTINCT col) FROM table GROUP BY col; -- MySQL 版本需支援
編寫查詢前對條件排序:真正高效的條件(能快速縮減資料量)應為主要驅動力。
SELECT DISTINCT c.custname FROM customers c JOIN orders o ON o.custid = c.custid JOIN orderdetail od ON od.ordid = o.ordid JOIN articles a ON a.artid = od.artid WHERE c.city = 'GOTHAM' AND a.artname = 'BATMOBILE' AND o.ordered >= ...;
弊端:
1. DISTINCT 掩蓋缺少連接條件導致的重複行。
2. 強制最終排序去重,開銷大。
3. 同名不同客戶會被誤去重(應返回 custid, custname)。
SELECT c.custname FROM customers c
WHERE c.city = 'GOTHAM'
AND EXISTS (
SELECT 1 FROM orders o, orderdetail od, articles a
WHERE a.artname = 'BATMOBILE'
AND a.artid = od.artid AND od.ordid = o.ordid
AND o.custid = c.custid AND o.ordered >= ...
);
DISTINCT,客戶唯一性由主鍵保證。c.custid。SELECT custname FROM customers
WHERE city = 'GOTHAM'
AND custid IN (
SELECT o.custid FROM orders o, orderdetail od, articles a
WHERE a.artname = 'BATMOBILE' AND a.artid = od.artid
AND od.ordid = o.ordid AND o.ordered >= ...
);
IN 不在乎重複值)。orderdetail.artid 有索引,先按商品名過濾再連接訂單更快。SELECT custname FROM customers
WHERE city = 'GOTHAM'
AND custid IN (
SELECT o.custid FROM orders o
JOIN (SELECT DISTINCT od.ordid FROM orderdetail od, articles a
WHERE a.artname = 'BATMOBILE' AND a.artid = od.artid) x
ON x.ordid = o.ordid
WHERE o.ordered >= ...
);
改寫為 FROM 子查詢時,**必須顯式 DISTINCT** 去重,否則語義變化。
EXISTS/IN 子查詢表達「存在性」,消除去重排序開銷。IN 還是 EXISTS 取決於最強過濾條件位置(商品名 vs 日期)。EXPLAIN 驗證執行計劃不可少。