語法
SELECT DISTINCT column_list FROM table_name [WHERE ...];
核心行為
- 對 結果集投影列 進行整行去重
DISTINCT col1, col2 ≠ DISTINCT col1 + DISTINCT col2,而是 (col1, col2) 元組去重
NULL 視為相同值(所有 NULL 視為一組)
- 可與聚合函數共用:
COUNT(DISTINCT col)
示例數據
CREATE TABLE `orders` (
`id` INT PRIMARY KEY,
`customer` VARCHAR(20),
`product` VARCHAR(20),
`amount` DECIMAL(10,2)
);
INSERT INTO `orders` VALUES
(1, 'Alice', 'Apple', 10),
(2, 'Alice', 'Banana', 20),
(3, 'Bob', 'Apple', 15),
(4, 'Alice', 'Apple', 10); -- 完全重複行
各類去重效果
| 查詢 | 結果行數 | 說明 |
SELECT DISTINCT customer FROM orders | 2 | Alice, Bob |
SELECT DISTINCT customer, product FROM orders | 3 | (Alice,Apple), (Alice,Banana), (Bob,Apple) |
SELECT DISTINCT * FROM orders | 3 | 第 4 行與第 1 行完全相同被去重 |
SELECT COUNT(DISTINCT customer) FROM orders | 1 行,值=2 | 聚合去重計數 |
DISTINCT vs GROUP BY
| 維度 | DISTINCT | GROUP BY |
| 語義 | 結果集去重 | 分組聚合 |
| 可用聚合函數 | 不可(除 COUNT(DISTINCT)) | 可(SUM, AVG, MAX 等) |
| 排序 | 無保證(實現常排序) | 無保證(需 ORDER BY) |
| 性能 | 相同(內部同樣用臨時表/排序去重) | 相同 |
| 擴展性 | 單純去重 | 支持 HAVING、WITH ROLLUP、窗口函數 |
性能優化
- 只選必要列:列越多,比較/排序/臨時表越大
- 去重列加索引:
INDEX (customer, product) 可避免臨時表(Index Full Scan + 去重)
- 大表分批:
INSERT INTO tmp SELECT DISTINCT ... FROM big_table LIMIT 100000 循環
- 近似去重:HyperLogLog (Redis) /
APPROX_COUNT_DISTINCT (8.0.13+)
實戰場景
1. 統計 UV(獨立訪客)
-- 精確
SELECT COUNT(DISTINCT user_id) FROM page_views WHERE date = '2024-01-15';
-- 近似(百萬級以上,誤差 < 2%)
SELECT APPROX_COUNT_DISTINCT(user_id) FROM page_views WHERE date = '2024-01-15';
2. 獲取不重複的標籤列表
SELECT DISTINCT tag FROM article_tags ORDER BY tag;
3. 找出重複數據(反向)
SELECT customer, product, COUNT(*) AS cnt
FROM orders
GROUP BY customer, product
HAVING COUNT(*) > 1;
常見誤區
| 誤區 | 真相 |
| DISTINCT 加快查詢 | 增加去重開銷,僅在需去重時使用 |
| DISTINCT 只對第一列生效 | 對 SELECT 列表所有列組合去重 |
| DISTINCT 可用於單列並保留其他列原值 | 不可,需配合 GROUP BY 或窗口函數 |
| SELECT DISTINCT a, b 等價於 SELECT a, b GROUP BY a, b | 結果相同,但 GROUP BY 可加聚合、HAVING、ROLLUP |
MySQL 8.0 新特性
APPROX_COUNT_DISTINCT(expr):近似計數,極快,適合大數據儀表盤
DISTINCT 內部優化:優先嘗試 Loose Index Scan(鬆散索引掃描)避免臨時表
- CTE + 窗口函數替代複雜 DISTINCT 邏輯