用rowid删重复行需主子查询group by字段完全一致,否则分组错位致漏删或误删;not in遇null失效,应改用not exists;min/max(rowid)选择取决于保留原始记录或最新版本的业务需求。

用 ROWID 删除重复行时,为什么必须配对写两个 GROUP BY?
因为 Oracle 要求主查询和子查询中用于去重的字段组合完全一致,否则 HAVING COUNT(*) > 1 的筛选结果与 MIN(ROWID) 的分组范围错位,轻则漏删,重则误删整表。
- 主查询的
WHERE (name, email) IN (...)和子查询的GROUP BY name, email必须字段顺序、数量、是否允许 NULL 完全相同 - 如果只在主查询加
WHERE name IS NOT NULL AND email IS NOT NULL,子查询却没加,NULL 行会被归为一组,MIN(ROWID)只返回一个,其余全删 - 字段少写一个(比如漏掉
email),就变成按单字段分组,逻辑彻底跑偏
NOT IN 子句里含 NULL 会导致整条 DELETE 无效果
这是最隐蔽也最难排查的问题:只要子查询返回任意一个 NULL,NOT IN 整个条件恒为 UNKNOWN,最终 DELETE 一行都不删,还报“已处理 0 行”——看起来成功,实则失效。
- 常见诱因:去重字段本身可为空,且实际存在 NULL 值
- 别用
NOT IN (SELECT MIN(ROWID) FROM t GROUP BY x, y),改用NOT EXISTS - 正确写法示例:
DELETE FROM customer a WHERE EXISTS ( SELECT 1 FROM customer b WHERE b.name = a.name AND b.email = a.email GROUP BY b.name, b.email HAVING COUNT(*) > 1 ) AND NOT EXISTS ( SELECT 1 FROM customer c WHERE c.name = a.name AND c.email = a.email AND c.ROWID = a.ROWID AND c.ROWID = ( SELECT MIN(d.ROWID) FROM customer d WHERE d.name = a.name AND d.email = a.email ) );
用 MAX(ROWID) 还是 MIN(ROWID)?关键看业务含义
ROWID 不是插入时间戳,但通常 MIN(ROWID) 更接近最早插入的那行(取决于数据块分配顺序),MAX(ROWID) 更可能对应最新插入的行。选哪个不是技术问题,而是业务决策。
- 保留“原始录入记录” → 用
MIN(ROWID) - 保留“最后更新版本” → 用
MAX(ROWID) - 不要混用:比如主查询用
MIN,子查询却用MAX,结果不可控 - 执行前务必先跑一遍
SELECT COUNT(*) FROM (SELECT MIN(ROWID) FROM t GROUP BY x, y),确认结果数等于你预期的“去重后行数”
临时表方案看似简单,但要注意主键和约束丢失
用 CREATE TABLE tmp AS SELECT DISTINCT * FROM t 再 TRUNCATE + INSERT 是可行路径,但容易忽略元数据层面的破坏。
- 原表的主键、外键、索引、触发器、权限全部丢失,需手动重建
-
DISTINCT对含 LOB 或 LONG 类型的列会报错,不适用 - 如果表上有
NOT NULL约束但某字段值全为 NULL,DISTINCT后仍保留该列,但后续插入可能违反约束 - 大表慎用:
SELECT DISTINCT会触发全表排序,内存/临时表空间压力大
真正难的不是写出能跑的语句,而是确保它在 NULL、并发写入、统计信息过期、分区表等现实条件下依然安全。每条 DELETE 执行前,先用 SELECT 模拟子查询结果,再核对行数,比事后恢复快十倍。











