如何实现PL/SQL多列去重_ROW_NUMBER窗口函数与PARTITION BY

2次阅读

用 ROW_NUMBER() + PARTITION BY + WHERE rn = 1 实现逻辑去重,按多列分组并依 ORDER BY 规则保留每组首行,可精准控制留哪条;DISTINCT 无法指定保留策略。

怎么用 ROW_NUMBER() 对多列组合去重

直接结论:靠 row_number() + partition by 配合 where rn = 1 实现逻辑去重,不是真删数据,而是筛选出每组的“代表行”。

典型场景是清洗脏数据——比如订单表里 order_idcustomer_idproduct_id 完全相同的重复记录,只留一条。

  • PARTITION BY 后面跟的是“去重维度”,即你认为构成重复的那些列,比如 PARTITION BY order_id, customer_id, product_id
  • ORDER BY 决定哪条被留下:按时间戳升序就留最早的一条,降序就留最新的一条;没业务意义时可固定写 ORDER BY NULL(但 oracle 12c+ 要求必须有 ORDER BY,不能真写 NULL,得用 ORDER BY 1 或加个常量
  • 别忘了给 ROW_NUMBER() 起别名(如 rn),否则外层 WHERE 没法引用

为什么 ROW_NUMBER()DISTINCT 更适合多列去重

DISTINCT 只能返回去重后的完整行,无法控制“留哪一条”;而真实业务中往往要留最新修改的、或状态为 'ACTIVE' 的那条。

比如用户表有 user_idemailstatusupdated_at,相同 email 多次注册,你想留 status = 'VERIFIED'updated_at 最大的那条——DISTINCT 做不到,但 ROW_NUMBER() 可以:

select user_id, email, status, updated_at FROM (   SELECT user_id, email, status, updated_at,          ROW_NUMBER() OVER (            PARTITION BY email             ORDER BY CASE WHEN status = 'VERIFIED' THEN 1 ELSE 0 END DESC,                      updated_at DESC          ) AS rn   FROM users ) WHERE rn = 1;
  • 这里用了表达式排序:先按验证状态优先级,再按时间,确保业务规则落地
  • 注意 CASE 表达式必须出现在 ORDER BY 里,不能只在 SELECT 列表中
  • Oracle 对 ORDER BY 子句里的表达式支持良好,但某些旧版本对复杂表达式排序性能较差,可考虑加函数索引

PARTITION BY 写错列会怎样

最常见错误:漏掉关键列,导致本该合并的没合并;或多写了列,把本该保留的拆散了。

例如日志表 log_timehostserviceerror_code,你想按“主机+服务+错误码”聚合去重,却只写 PARTITION BY host, service——结果同一台机器上不同错误码也被当成一组,只留一条,丢数据。

  • 检查方法:先跑子查询,查几组 count(*)MIN(log_time), MAX(log_time),确认分组逻辑符合预期
  • 如果列里有 NULL,Oracle 默认把所有 NULL 归为同一组(和 sql 标准一致),但业务上可能希望 NULL 不参与分组——得提前用 NVL(col, '###')COALESCE 处理
  • PARTITION BY 列顺序无关,但建议按选择性从高到低排列(比如先 order_idline_no),对 CBO 优化器更友好

性能和兼容性要注意什么

大表上用 ROW_NUMBER() 去重,本质是全表扫描 + 排序,容易慢,尤其当 PARTITION BY 维度基数低(比如只有几个 status 值),会导致单个分区极大。

  • 务必在 PARTITION BY 列上建组合索引,最好包含 ORDER BY 列(如 (email, updated_at)),让排序走索引范围扫描
  • Oracle 11g 及以前不支持分析函数在 WHERE 子句直接使用(会报 ORA-30483),必须套一层子查询;12c+ 支持内联视图优化,但语法不变
  • 别在 PL/SQL 块里反复执行这种语句——每次都会硬解析;封装成视图或带参数的函数更稳妥

真正麻烦的不是语法,是搞清“哪些列才算重复”——这得翻需求文档、对业务字段含义,而不是看表结构名字就拍板。

text=ZqhQzanResources