select distinct 作用于整行字段组合,非单列去重;null视为相等;不可与聚合函数混用;order by字段须在select列表中;去重发生在最终结果集。

SELECT DISTINCT 只能作用于指定列,不能隐式保留其他列
直接写 SELECT DISTINCT name FROM users 是安全的,但很多人误以为加了 DISTINCT 就能“去重某列同时带出其他字段”,比如 SELECT DISTINCT name, email FROM users —— 这其实是按 (name, email) 组合去重,不是只对 name 去重。如果同一 name 对应多个 email,这条语句会返回多行。
真正只对单列去重、又想附带某条关联记录(如最新一条),得用子查询或窗口函数:
- 用
ROW_NUMBER():先按name分组,再按时间倒序排,取每组rn = 1的行 - 用聚合 +
GROUP BY:如SELECT name, MAX(created_at) FROM users GROUP BY name,但无法直接拿到对应那行的email - 避免用
SELECT DISTINCT name, * FROM users—— 语法错误,*和单列混用不合法
DISTINCT 在 WHERE 或 ORDER BY 中不生效
DISTINCT 是 SELECT 子句的修饰符,只影响最终结果集。它不会改变 WHERE 的过滤逻辑,也不会让 ORDER BY 只对去重后的数据排序(实际上 ORDER BY 总是作用于去重后的结果)。
常见误解示例:
-
SELECT DISTINCT name FROM users WHERE name IS NOT NULL ORDER BY id——ORDER BY id会报错,因为id不在SELECT列表中(除非也加进SELECT,但那就破坏“单列去重”目标) - 想“先按
name去重,再查其中id > 100的”,不能靠DISTINCT配合WHERE实现,得用派生表或 CTE
性能开销比想象中大,尤其没索引时
DISTINCT 本质是隐式 GROUP BY,数据库要对结果集做哈希或排序去重。当 SELECT DISTINCT name FROM huge_table 执行慢,大概率是因为 name 列没索引,导致全表扫描+内存排序。
- 检查执行计划:PostgreSQL 用
EXPLAIN,MySQL 用EXPLAIN FORMAT=TRADITIONAL,看是否出现HashAggregate或Using filesort - 加索引有效:对高频去重列建单列索引,如
CREATE INDEX idx_users_name ON users(name) - 注意索引长度:MySQL 中
VARCHAR(255)默认只索引前 767 字节,超长字段需显式指定前缀长度
NULL 值被当作相同值处理,且只保留一个
SQL 标准规定:所有 NULL 在 DISTINCT 中视为相等。所以 SELECT DISTINCT status FROM orders 中,哪怕有 100 行 status IS NULL,结果里也只出现一个 NULL。
这和 GROUP BY 行为一致,但容易被忽略:
- 如果业务上需要区分“未填写”和“明确为空字符串”,得提前把
NULL转成特定字符串,如SELECT DISTINCT COALESCE(status, '[null]') FROM orders - 某些场景下,
NULL去重后消失可能掩盖数据质量问题,建议先用COUNT(*)和COUNT(status)对比确认空值比例
实际写单列去重时,最常卡住的不是语法,而是搞不清“到底要的是唯一值列表”,还是“每个唯一值对应的某条代表记录”。前者用 SELECT DISTINCT col 没问题;后者必须跳出 DISTINCT 思维,用聚合、窗口函数或关联子查询。











