如何删除没有唯一标识符的重复行

时间:2014-11-05 23:58:45

标签: sql postgresql duplicates netezza

我的表中有重复的行,我想以最有效的方式删除重复项,因为表很大。经过一番研究,我得出了这个问题:

WITH TempEmp AS
(
SELECT name, ROW_NUMBER() OVER(PARTITION by name, address, zipcode ORDER BY name) AS duplicateRecCount
FROM mytable
)
-- Now Delete Duplicate Records
DELETE FROM TempEmp
WHERE duplicateRecCount > 1;

但它只适用于SQL,而不适用于Netezza。它似乎不喜欢DELETE子句之后的WITH

10 个答案:

答案 0 :(得分:52)

如果您没有其他唯一标识符,则可以使用ctid

delete from mytable
    where exists (select 1
                  from mytable t2
                  where t2.name = mytable.name and
                        t2.address = mytable.address and
                        t2.zip = mytable.zip and
                        t2.ctid > mytable.ctid
                 );

在每个表中都有一个唯一的自动递增ID是个好主意。像这样做delete是一个重要原因。

答案 1 :(得分:28)

我喜欢@ erwin-brandstetter的解决方案,但希望使用USING关键字显示解决方案:

DELETE   FROM table_with_dups T1
  USING       table_with_dups T2
WHERE  T1.ctid    < T2.ctid       -- delete the "older" ones
  AND  T1.name    = T2.name       -- list columns that define duplicates
  AND  T1.address = T2.address
  AND  T1.zipcode = T2.zipcode;

如果您想在删除记录之前查看记录,只需将DELETE替换为SELECT *,将USING替换为逗号,,即

SELECT * FROM table_with_dups T1
  ,           table_with_dups T2
WHERE  T1.ctid    < T2.ctid       -- select the "older" ones
  AND  T1.name    = T2.name       -- list columns that define duplicates
  AND  T1.address = T2.address
  AND  T1.zipcode = T2.zipcode;

更新:我在这里测试了一些不同的解决方案以提高速度。如果你不期望有很多重复项,那么这个解决方案比那些具有NOT IN (...)子句的解决方案要好得多,因为它们会在子查询中生成很多行。

如果您重写查询以使用IN (...),那么它的执行方式与此处提供的解决方案类似,但SQL代码变得简洁得多。

更新2:如果其中一个键列中有NULL个值(您真的不应该IMO),那么您可以在该列的条件中使用COALESCE(),例如

  AND COALESCE(T1.col_with_nulls, '[NULL]') = COALESCE(T2.col_with_nulls, '[NULL]')

答案 2 :(得分:19)

在一个完美的世界中,每个表都有一个唯一的标识符。
如果没有任何唯一列(或其组合),请使用the ctid column

DELETE FROM tbl
WHERE  ctid NOT IN (
   SELECT min(ctid)                    -- ctid is NOT NULL by definition
   FROM   tbl
   GROUP  BY name, address, zipcode);  -- list columns defining duplicates

上述查询很简单,只列出一次列名。当可以涉及NULL值时,NOT IN (SELECT ...)是一种棘手的查询样式,但系统列ctid永远不会为NULL。参见:

EXISTS用作demonstrated by @Gordon通常会更快。使用USING子句like @isapir added later的自联接也是如此。两者都应该导致相同的查询计划。

但请注意重要差异:这些其他查询将 NULL 值视为 不等于 GROUP BY(或DISTINCTDISTINCT ON ())将NULL值视为相等。如果定义了键列NOT NULL,则无关紧要。否则,根据您对“重复”的定义,您将需要一种或另一种方法。 使用IS NOT DISTINCT FROM比较值(可能无法使用某些索引)。

声明:

ctid是Postgres的内部实现细节,它不在SQL标准中,可以在没有警告的情况下在主要版本之间进行更改(即使这不太可能)。由于后台进程或并发写操作(但不在同一命令中),其值可能会在命令之间发生变化。

相关:

除了:

DELETE语句的目标不能是CTE,只能是基础表。这是SQL Server的溢出效应 - 就像你的整个方法一样。

答案 3 :(得分:10)

以下是我提出的问题,使用group by

DELETE FROM mytable
WHERE id NOT in (
  SELECT MIN(id) 
  FROM mytable
  GROUP BY name, address, zipcode
)

删除重复项,保留最旧的重复记录。

答案 4 :(得分:6)

我们可以使用窗口函数来非常有效地删除重复的行:

DELETE FROM tab 
  WHERE id IN (SELECT id 
                  FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), id 
                           FROM tab) x 
                 WHERE x.row_number > 1);

一些PostgreSQL的优化版本(带有ctid):

DELETE FROM tab 
  WHERE ctid = ANY(ARRAY(SELECT ctid 
                  FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), ctid 
                           FROM tab) x 
                 WHERE x.row_number > 1));

答案 5 :(得分:2)

有效语法在http://www.postgresql.org/docs/current/static/sql-delete.html

指定

我会更改您的表以添加唯一的自动递增主键ID,以便您可以运行如下所示的查询,该查询将保留每组重复项中的第一个(即具有最低ID的那一个)。请注意,在Postgres中添加密钥比其他一些DB更复杂一些。

DELETE FROM mytable d USING (
  SELECT min(id), name, address, zip 
  FROM mytable 
  GROUP BY name, address, zip HAVING COUNT() > 1
) AS k 
WHERE d.id <> k.id 
AND d.name=k.name 
AND d.address=k.address 
AND d.zip=k.zip;

答案 6 :(得分:1)

如果您想在表格中保留一行重复的行。

create table some_name_for_new_table as 
(select * from (select *,row_number() over (partition by pk_id) row_n from 
your_table_name_where_duplicates_are_present) a where row_n = 1);

这将创建一个可以复制的表。

在复制表格之前,请删除列&#39; row_n&#39;

答案 7 :(得分:0)

来自文档delete duplicate rows

IRC中的一个常见问题是如何删除在一组列上重复的行,只保留ID最低的行。 此查询对具有相同column1,column2和column3的所有tablename行执行此操作。

DELETE FROM tablename
WHERE id IN (SELECT id
          FROM (SELECT id,
                         ROW_NUMBER() OVER (partition BY column1, column2, column3 ORDER BY id) AS rnum
                 FROM tablename) t
          WHERE t.rnum > 1);

有时会使用时间戳字段而不是ID字段。

答案 8 :(得分:0)

如果您想为每一行添加唯一标识符,您只需添加一个(序列号或guid),并将其视为代理键

CREATE TABLE thenames
        ( name text not null
        , address text not null
        , zipcode text not null
        );
INSERT INTO thenames(name,address,zipcode) VALUES
('James', 'main street', '123' )
,('James', 'main street', '123' )
,('James', 'void street', '456')
,('Alice', 'union square' , '123')
        ;

SELECT*FROM thenames;
        -- add a surrogate key
ALTER TABLE thenames
        ADD COLUMN seq serial NOT NULL PRIMARY KEY
        ;
SELECT*FROM thenames;

DELETE FROM thenames del
WHERE EXISTS(
        SELECT*FROM thenames x
        WHERE x.name=del.name
        AND x.address=del.address
        AND x.zipcode=del.zipcode
        AND x.seq < del.seq
        );

        -- add the unique constrain,so that new dupplicates cannot be created in the future
ALTER TABLE thenames
        ADD UNIQUE (name,address,zipcode)
        ;

SELECT*FROM thenames;

答案 9 :(得分:0)

对于较小的表,我们可以使用rowid伪列来删除重复的行。

您可以在下面使用此查询:

Delete from table1 t1 where t1.rowid > (select min(t2.rowid) from table1 t2 where t1.column = t2.column)