DISTINCT作用于整行而非单列,仅当所有SELECT字段值完全相同时才去重;语法必须为SELECT DISTINCT col1, col2,不可修饰单字段;NULL被视为相同值;单纯取唯一值时最轻量,但无法按某列去重后返回其他字段完整记录。

用 DISTINCT 去重是最直接的方式
如果你只需要“不重复的值”,而不是“整行唯一”,DISTINCT 是最轻量、最常用的方法。它作用于查询结果的**整个行组合**,不是单个字段。
常见错误是以为 DISTINCT col1 只对 col1 去重,其实只要 SELECT 后有多个字段,DISTINCT 就会对所有字段组成的元组去重。
-
SELECT DISTINCT name FROM users→ 返回唯一的name值(忽略同名不同邮箱的情况) -
SELECT DISTINCT name, email FROM users→ 只有name和email都完全相同时才去重 - 不能写成
SELECT name, DISTINCT email—— 语法错误,DISTINCT必须紧贴SELECT
查出“完全不重复的整行”要用 GROUP BY + HAVING COUNT(*) = 1
当你要找的是“在表中只出现一次的完整记录”(即没有其他行和它所有字段都相同),DISTINCT 不够用——它只返回去重后的结果,不告诉你哪些行是唯一的。
这时得靠分组统计:把每行当作一个组,看它是否只出现一次。
SELECT name, email FROM users GROUP BY name, email HAVING COUNT(*) = 1- 必须把
SELECT中的所有非聚合字段都放进GROUP BY,否则多数数据库(如 MySQL 严格模式、PostgreSQL)会报错 - 注意 NULL 值:多行
NULL在GROUP BY中会被视为同一组,所以如果email是NULL,多条name='A', email=NULL会被合并计数
想查“某字段值不重复,但要保留整行原始数据”得用窗口函数
比如你有一张订单表,想找出每个 user_id 的**第一条订单**(按时间),而不是简单去重 user_id —— 这时 DISTINCT 和 GROUP BY 都没法直接返回你想要的那条完整记录。
窗口函数是标准解法:
SELECT user_id, order_id, created_at
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn
FROM orders
) t
WHERE rn = 1;
-
PARTITION BY user_id表示按用户分组;ORDER BY created_at决定哪条是“第一” -
ROW_NUMBER()给每组内行编号,RN = 1就是每组首行 - 如果允许并列(比如同时间两条订单都算“最早”),改用
RANK()或DENSE_RANK() - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 也支持
性能和兼容性要注意的几个点
去重操作天然容易慢,尤其没索引或字段含大量 NULL 时。
-
DISTINCT在大数据量下可能触发临时磁盘排序,加EXPLAIN看是否用了Using filesort或Using temporary -
GROUP BY去重比DISTINCT更重,因为要实际计数;若只是取唯一值,优先用DISTINCT - 旧版 MySQL(5.7 及之前)默认允许
SELECT a,b FROM t GROUP BY a(只按部分字段分组),但这行为不可靠,不同 SQL 模式下结果可能不一致,应避免 - 如果表里有大字段(如
TEXT、JSON),DISTINCT或GROUP BY可能因无法索引而严重拖慢
真正难的不是语法,而是想清楚你要的到底是“值唯一”“行唯一”还是“每组取一行”——这三个问题对应三种完全不同且不能混用的写法。

















