distinct仅作用于select列表,修饰整行而非单列;不能用于where或order by;NULL被视为相同值;复杂去重需用group by或row_number()。

distinct 只能作用于 select 列表,不能用于 where 或 order by
很多人误以为 DISTINCT 是个独立指令,可以像函数一样“对某列去重”,其实它修饰的是整个 SELECT 行。只要两行在 SELECT 中列出的所有字段组合完全一致,就只保留一行。
常见错误写法:SELECT DISTINCT name, age FROM user WHERE DISTINCT city = 'Beijing' —— 这会直接报错,因为 DISTINCT 不是关键字,不能出现在 WHERE 子句里。
-
DISTINCT必须紧跟在SELECT后面,且只能出现一次 - 如果选了
SELECT DISTINCT name, age,那么(张三, 25)和(张三, 26)算两行,不会被去重 - 想按单列逻辑去重但又想取其他字段?不能靠
DISTINCT直接实现,得用GROUP BY或窗口函数
distinct 和 group by 在语义和性能上并不等价
DISTINCT 看似简单,但 mysql 内部常把它重写为隐式 GROUP BY(尤其在有聚合函数时),而显式 GROUP BY 允许你控制聚合逻辑、使用 ANY_VALUE() 或 MIN()/MAX() 选取代表值。
例如:要取每个 city 下任意一条用户记录(含 id、name、age):
SELECT DISTINCT city, name, age FROM user;
这并不能保证“每个 city 只返回一条”,因为 city 相同但 name 或 age 不同时仍会多行;真正想要的其实是:
SELECT city, ANY_VALUE(name), ANY_VALUE(age) FROM user GROUP BY city;
- MySQL 5.7+ 开启
ONLY_FULL_GROUP_BY时,SELECT city, name FROM user GROUP BY city会报错,必须对非分组列用聚合函数包裹 -
ANY_VALUE()是最轻量的“选一个就行”的方式,不保证稳定性,但性能好 -
DISTINCT无法表达“每组取最新一条”这种需求,必须配合子查询或ROW_NUMBER()(MySQL 8.0+)
distinct 对 null 值的处理:多个 null 被视为相同
这是容易被忽略的细节:NULL 在 DISTINCT 中被当作相等值处理。比如:
SELECT DISTINCT status FROM orders;
若 status 有 'paid'、'shipped'、NULL、NULL,结果只有三行 —— 所有 NULL 合并为一个。
- 这点和
GROUP BY一致,但和ORDER BY不同(ORDER BY col ASC中NULL默认排最前) - 如果业务上需要区分“未填”和“明确为空”,建议用字符串如
'unknown'替代NULL,避免去重逻辑意外吞掉数据 - 联合去重时,
(1, NULL)和(1, NULL)被去重,但(1, NULL)和(1, 'a')不会被去重
替代 distinct 的更可控方案:row_number() + 子查询(MySQL 8.0+)
当你要“每个分组取最新/最早/指定排序下的一条”,DISTINCT 完全无能为力,必须换思路。
例如:每个 user_id 取最新一条订单(按 created_at 降序):
SELECT user_id, order_no, created_at FROM ( SELECT user_id, order_no, created_at, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders ) t WHERE rn = 1;
- 这个模式比
GROUP BY + MAX(created_at)再关联原表更简洁,也避免了关联丢失字段的风险 - 注意
ROW_NUMBER()是窗口函数,必须在子查询或 CTE 中使用,不能直接在外部WHERE里引用别名rn - 如果用的是 MySQL 5.7 或更低版本,只能用自连接或相关子查询,性能差很多,建议升级或加索引优化
实际去重操作中最容易卡住的地方,不是语法写不对,而是没想清楚“到底要按什么维度去重”以及“其他字段怎么选”。DISTINCT 只解决“整行唯一”这一种情况,稍复杂一点的业务场景,就得切换到 GROUP BY 或窗口函数。