DISTINCT按整行去重,非单字段;一对多JOIN易致重复,需用GROUP BY或窗口函数;LEFT JOIN后WHERE过滤右表会变INNER JOIN;存在性检查宜用EXISTS而非IN。

为什么 DISTINCT 没挡住重复行?
因为 DISTINCT 是对整行去重,不是按某个字段“去重”。只要任意一列值不同,两行就被视为不同——哪怕你只关心 user_id,但 JOIN 后带了多个 order_id、product_name,结果自然膨胀。
常见错误现象:SELECT DISTINCT user_id FROM users u JOIN orders o ON u.id = o.user_id 仍返回多条相同 user_id —— 这说明一个用户有多个订单,而 DISTINCT 看的是整行(含 o.id、o.created_at 等),不是只看 user_id。
- 别指望
DISTINCT代替逻辑去重;它只是“筛掉完全一样的行” - 如果目标是“每个用户只取一条订单”,得用
GROUP BY或窗口函数,不是DISTINCT -
DISTINCT会隐式排序(某些数据库),可能拖慢大表查询
JOIN 条件漏写或写错导致笛卡尔积
这是重复最猛的来源:没加 ON 条件,或关联字段类型不一致(比如 INT 对 VARCHAR),让数据库无法正确匹配,退化成交叉连接。
使用场景:多表关联时,尤其涉及中间表(如 user_role)、历史快照表、或 LEFT JOIN 后又加了 WHERE 过滤右表字段(把 LEFT 变成 INNER)。
- 检查执行计划,看
rows是否异常高;MySQL 用EXPLAIN,PostgreSQL 用EXPLAIN ANALYZE - 确认所有 JOIN 字段类型一致,必要时显式转换:
ON u.id = CAST(o.user_id AS INTEGER) - LEFT JOIN 后避免写
WHERE o.status = 'paid',这会让左表没匹配的行也被过滤掉;应改用AND o.status = 'paid'放在ON子句里
一对多关系下该用 GROUP BY 还是窗口函数?
取决于你要什么数据:聚合统计(如用户订单数)用 GROUP BY;要保留明细但只取最新/某一条,用窗口函数更稳。
参数差异:GROUP BY 必须把 SELECT 中非聚合字段全写进分组项,容易漏;窗口函数靠 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) 精准控制每组取哪行。
- 要“每个用户最新一笔订单”:用
ROW_NUMBER()+ 外层WHERE rn = 1 - 要“每个用户订单总数+平均金额”:用
GROUP BY user_id+COUNT(*)、AVG(amount) - SQLite 不支持窗口函数(3.25+ 才支持),老版本只能用相关子查询或临时表
用 EXISTS 替代 IN 或 JRIN 避免重复和 NULL 陷阱
当只需要判断“是否存在关联记录”,而不是取关联字段内容时,EXISTS 更安全、更高效,且天然规避因 NULL 导致的逻辑错误和重复放大。
性能影响:相比 JOIN,EXISTS 在找到第一条匹配就停止扫描,适合“存在性检查”类场景;而 JOIN 会把所有匹配都拉出来再处理。
- 错误写法:
WHERE user_id IN (SELECT user_id FROM orders WHERE status = 'pending')—— 若子查询返回 NULL,整个条件变 UNKNOWN,结果为空 - 正确替代:
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'pending') - 别在
EXISTS子查询里 SELECT *;写SELECT 1更清晰,也避免优化器误判
真正难的不是语法,是想清楚你要的是“存在性”“聚合值”还是“某一条明细”——选错语义,后面怎么加 DISTINCT 都是徒劳。










