select distinct没去重是因为它按整行字段组合全匹配去重,而非单列;若含id、时间戳等唯一字段,或join一对多引入子表差异字段,或null与空字符串混用,均导致逻辑不重复。

SELECT DISTINCT 为什么没去重?因为你在查“整行”,不是“某列”
加了 DISTINCT 还有重复,大概率不是语法错了,而是你 SELECT 的字段组合本身就不重复——哪怕业务上看着一样。比如查用户和订单:SELECT DISTINCT u.name, o.order_no,只要任意一个 order_no 不同,整行就算不同,DISTINCT 就不会合并。
常见诱因:
- 无意中带入了唯一字段(如主键
id、时间戳created_at、自增序号) - JOIN 一对多时,子表字段(如
item_id、quantity)参与了结果拼接,导致“同一用户+不同商品”生成多行 - 字段值看似相同,实则存在隐形差异:尾部空格、大小写(PostgreSQL 区分)、不可见字符(如
\u00A0)、或NULL与空字符串''混用
NULL 和空字符串让 DISTINCT “失效”
DISTINCT 把所有 NULL 视为相等,但 NULL 和 ''(空字符串)是两个完全不同的值,永远不重复。如果某列既有 NULL 又有 '',它们会各自保留一行,看起来像“没去重”。
实操建议:
- 统一空值表示:
COALESCE(status, 'UNKNOWN')或NULLIF(trim(col), '') - 清理尾部空格:
TRIM(name) - 统一大小写:
LOWER(email)(尤其在 PostgreSQL 等区分大小写的库中) - 避免混用:
WHERE col IS NULL OR col = ''要显式处理,不能指望DISTINCT合并
DISTINCT 放错位置,等于没加
嵌套查询里把 DISTINCT 写在外层,而内层 JOIN 或子查询已经产生笛卡尔积,外层只是对一堆“本来就不一样的行”再跑一遍去重——性能差,还掩盖逻辑问题。
典型错误写法:
SELECT DISTINCT a.* FROM (SELECT u.id, u.name, o.order_id FROM users u JOIN orders o ON u.id = o.user_id) a
正确思路:
- 先确认重复根源:执行不加
DISTINCT的原始查询,SELECT *看哪几列在变 - 若只需主表数据,优先用
EXISTS替代JOIN:SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id) - 若必须关联子表信息,改用窗口函数控制“留哪一条”:
ROW_NUMBER() OVER (PARTITION BY u.id ORDER BY o.created_at DESC),再过滤rn = 1
想按某列去重、同时取其他字段?DISTINCT 做不到
DISTINCT 是无状态的:它只判断“这行要不要”,不决定“该留哪一行”。比如“每个邮箱只取最新一条用户记录”,SELECT DISTINCT email, created_at FROM users 无法保证 created_at 是最大值——数据库可能随机选一行。
替代方案:
- 用
GROUP BY+ 聚合:SELECT email, MAX(created_at) FROM users GROUP BY email - 用窗口函数精准控制:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn FROM users) SELECT * FROM ranked WHERE rn = 1 - 某些数据库支持非标语法(如 PostgreSQL 的
DISTINCT ON (email)),但跨库迁移风险高,不推荐作为通用解法
最常被忽略的一点:DISTINCT 对字段顺序和类型敏感。两个值看似一样,但一个是 VARCHAR、一个是 INTEGER,或者编码不同(UTF8 vs GBK),数据库就认为它们不同——这种差异往往藏在 JOIN 条件或视图定义里,肉眼难查。










