DISTINCT仅作用于子查询自身结果,JOIN后重复源于关联字段不唯一或条件未收敛;其去重基于整行逐字节比对,非业务逻辑判断;含聚合或窗口函数时冗余甚至报错;ORDER BY字段须在SELECT列表中,否则引发错误或顺序异常。

子查询里加了DISTINCT,外层JOIN后还是重复
根本不是DISTINCT没生效,而是它只作用于子查询自身结果——一旦这个“去重后”的结果被用作派生表参与JOIN,而关联字段(比如user_id)在主表中本身就不唯一,或JOIN条件没收敛,就会立刻产生笛卡尔积式膨胀。例如:SELECT * FROM orders o JOIN (SELECT DISTINCT user_id, region FROM users) u ON o.user_id = u.user_id,如果orders里一个user_id对应多条订单,那每条都会和u里该user_id的行匹配,重复就出来了。
- 先用
SELECT *查出原始JOIN结果,看哪几列在变——常是时间戳、明细ID、状态变更记录这类天然不唯一的字段 - 若只需主表数据,别JOIN子查询,改用
EXISTS或IN:把JOIN (SELECT DISTINCT ...)换成WHERE EXISTS (SELECT 1 FROM users u WHERE u.user_id = o.user_id AND u.status = 'active') -
DISTINCT必须写在子查询最内层SELECT开头,不能只在外层套一层SELECT DISTINCT *,否则只是对已膨胀的结果“事后补救”,性能差还掩盖问题
DISTINCT去不掉“看起来一样”的行
DISTINCT判断重复是逐字节比对整行所有字段值,不是按业务理解“算重复”。两行name相同但created_at差1毫秒、或lotno末尾多了个空格、或一个是NULL一个是空字符串'',全都被视为不同行。
- 检查重复行的差异字段:用
SELECT HEX(col)或LENGTH(col)确认是否有隐藏空格、不可见字符 - 对字符串字段预处理:
TRIM(LOWER(name))、COALESCE(status, 'UNKNOWN'),避免NULL和空值混用 - MySQL默认不区分大小写,PostgreSQL区分——跨库迁移时
DISTINCT行为可能突变
子查询含聚合或窗口函数时DISTINCT冗余甚至报错
当子查询已经用了GROUP BY或ROW_NUMBER(),再加DISTINCT不仅多余,还可能破坏逻辑或触发语法错误。比如SELECT DISTINCT user_id, MAX(create_time) FROM orders GROUP BY user_id,GROUP BY本身已保证user_id唯一,DISTINCT纯属干扰;而SELECT DISTINCT *, ROW_NUMBER() OVER (...) FROM t在多数数据库直接报错,因窗口函数要求明确的排序依据,DISTINCT会打乱行序。
- 先确认子查询语义是否已隐含唯一性:
GROUP BY主键、ROW_NUMBER() ... WHERE rn = 1等场景下删掉DISTINCT - 需要“按某列去重并保留完整行”时,
DISTINCT完全无能为力,必须用ROW_NUMBER()+ CTE或子查询过滤 - 别在
WHERE子句里嵌套窗口函数——ROW_NUMBER()只能出现在CTE、派生表或SELECT列表中
ORDER BY字段不在SELECT列表中导致DISTINCT失效错觉
很多人写SELECT DISTINCT name FROM users ORDER BY created_at,发现结果顺序不对甚至报错,误以为DISTINCT失效。其实是ORDER BY引用了未选字段,数据库要么拒绝执行(如MySQL 5.7+严格模式),要么按内部顺序返回,看起来像“没去重”。
-
ORDER BY字段必须出现在SELECT列表中,或为其定义别名后引用 - 想按未选字段排序,得改写成子查询:
SELECT name FROM (SELECT DISTINCT name, MAX(created_at) AS max_time FROM users GROUP BY name) t ORDER BY max_time - PostgreSQL支持
DISTINCT ON (col),但MySQL/SQL Server不兼容,线上别依赖
真正麻烦的从来不是DISTINCT写没写,而是你没想清楚:这行重复,到底是数据本身冗余、JOIN路径发散、字段组合天然不唯一,还是业务上根本就需要“留最新那一行”——这时候DISTINCT连入场券都没有。










