如何在Oracle中编写一个通用的过程来查找和删除任何表和列中的重复数据?
问题陈述
您希望编写一个通用的过程来查找和删除Oracle中任何表和列中的重复数据。
解决方案
我们可以使用Oracle的内部ROWID值来唯一标识表中的行,以及带有分区子句的OLAP函数row_number。实现此目的的示例语法如下所示。
delete from table where rowid in (... query here ...)
为了演示其用法,我们将首先创建示例数据。
示例
-- table with tennis player rankings DROP TABLE atp_stats; CREATE TABLE atp_stats ( player_rank NUMBER NOT NULL, player_name VARCHAR2(100) NOT NULL, time_range TIMESTAMP(6)); -- sample records INSERT INTO atp_stats VALUES (1,'ROGER FEDERER',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (2,'RAFAEL NADAL',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (3,'NOVAK DJOKOVIC',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (4,'ANDY MURRAY',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (1,'ROGER FEDERER',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (2,'RAFAEL NADAL',CURRENT_TIMESTAMP); INSERT INTO atp_stats VALUES (3,'NOVAK DJOKOVIC',CURRENT_TIMESTAMP); COMMIT;
看看我们刚刚创建的数据。
示例
SELECT * FROM atp_stats ORDER BY 2;
球员排名 | 球员姓名 |
4 | ANDY MURRAY |
3 | NOVAK DJOKOVIC |
3 | NOVAK DJOKOVIC |
2 | RAFAEL NADAL |
2 | RAFAEL NADAL |
1 | ROGER FEDERER |
1 | ROGER FEDERER |
因此,我们插入了3个重复项,我们希望将其删除。在我们继续编写Delete语句之前,让我们了解一下带有ROWID的内部查询。
示例
SELECT rowid FROM ( SELECT player_rank, player_rank, rowid , row_number() over (partition BY player_rank, player_name order by player_rank,player_name) AS rnk FROM atp_stats ) WHERE rnk > 1;
我故意将列player_rank和player_name添加到最内部的子查询中,以使逻辑易于理解。理想情况下,可以不使用它们来编写最内部的子查询,效果相同。如果我们仅执行此最内部的查询(当然,为了清晰起见,选择了额外的列),我们会看到这些结果。
球员排名 | 球员姓名 | rowid | rnk |
4 | ANDY MURRAY | AAAPHcAAAAAB/4TAAD | 1 |
3 | NOVAK DJOKOVIC | AAAPHcAAAAAB/4TAAC | 1 |
3 | NOVAK DJOKOVIC | AAAPHcAAAAAB/4TAAG | 2 |
2 | RAFAEL NADAL | AAAPHcAAAAAB/4TAAB | 1 |
2 | RAFAEL NADAL | AAAPHcAAAAAB/4TAAF | 2 |
1 | ROGER FEDERER | AAAPHcAAAAAB/4TAAE | 1 |
1 | ROGER FEDERER | AAAPHcAAAAAB/4TAAA | 2 |
SQL返回表中所有行的rowid。然后,ROW_NUMBER()函数在由PARTITION BY指令驱动的id和player_name的集合上运行。这意味着对于每个唯一的player_rank和player_name,ROW_NUMBER将启动我们已将其别名为rnk的行运行计数。当观察到新的player_rank和player_name组合时,rnk计数器将重置为1。
现在,我们可以应用DELETE运算符来删除重复值,如下所示。
SQL:删除重复项
示例
DELETE FROM atp_stats WHERE rowid IN ( SELECT rowid FROM( SELECT player_rank, player_name, rowid , row_number() over (partition BY player_rank, player_name order by player_rank,player_name) AS rnk FROM atp_stats ) WHERE rnk > 1 );
输出
3 rows deleted.
球员排名 | 球员姓名 |
4 | ANDY MURRAY |
3 | NOVAK DJOKOVIC |
2 | RAFAEL NADAL |
1 | ROGER FEDERER |
由于删除重复项是程序员执行的最常见任务之一,因此最好创建一个可重用过程。下面的过程将接受要从中删除重复项的表名以及用于搜索的列名。
首先,我们将为传递要分组的动态数量的列创建一个表类型。然后,我们将创建一个过程来动态删除数据。
代码:删除重复项的通用过程
示例
CREATE OR REPLACE TYPE tmp_args AS TABLE OF VARCHAR2(30); CREATE PROCEDURE remove_duplicates (p_table IN VARCHAR2, p_cols tmp_args) AS l_remve_dupl CLOB ; l_columns VARCHAR2(30); l_sql_count NUMBER; BEGIN -- get the columns and combine them as comma seperated value SELECT LISTAGG(COLUMN_VALUE, ',') WITHIN GROUP(ORDER BY COLUMN_VALUE) INTO l_columns FROM TABLE(p_cols); -- generate dynamic delete statement SELECT 'DELETE FROM ' || p_table || ' WHERE rowid IN ( SELECT rowid FROM( SELECT rowid , row_number() OVER (partition BY ' || l_columns || ' ORDER BY ' || l_columns || ') AS rnk FROM ' || p_table || ' ) WHERE rnk > 1 ) ' INTO l_remve_dupl FROM DUAL ; EXECUTE IMMEDIATE l_remve_dupl; l_sql_count := SQL%ROWCOUNT; COMMIT; END;
用法
BEGIN remove_duplicates('ATP_STATS', tmp_args('PLAYER_RANK','PLAYER_NAME')); END;
输出
球员排名 | 球员姓名 |
4 | ANDY MURRAY |
3 | NOVAK DJOKOVIC |
2 | RAFAEL NADAL |
1 | ROGER FEDERER |