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

Posted

技术标签:

【中文标题】如何删除没有唯一标识符的重复行【英文标题】:How to delete duplicate rows without unique identifier 【发布时间】:2015-01-02 08:50:23 【问题描述】:

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

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。好像不喜欢WITH子句后面的DELETE

【问题讨论】:

如果它是一次性工作 - 为什么不在 postgresql 控制台中运行它? 这不是一次工作,而是每周一次,我们总是得到一些重复的值。谢谢 为什么会得到重复值?如果你只是不把它放在第一位呢? (name, address, zipcode)定义了重复项吗?还有其他列吗?那些无关紧要吗?不同的?列的任何组合是唯一的吗?如果某些列在重复项之间存在差异,您要保留每组中的哪一行? 适用于 POSTGRESQL(也适用于 AWS REDSHIFT)View the answer to this question on another page 【参考方案1】:

我喜欢@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]')

【讨论】:

Erwin 的答案更好,因为它可以正确处理 NULL 值,并且不需要输入两次列名。 正如我在答案开头所写的那样:I like @erwin-brandstetter 's solution, but wanted to show a solution ...。不过,在发现性能优势后,我更喜欢USING 解决方案,尤其是对于大型表。我添加了一个示例,说明如何处理 NULL 值。 非常好,尤其是可以先看看。为了检查数据列中的 NULL 值,我根据表的 \dS 输出为每列生成了一个 T1.col = T2.col OR (T1.col IS NULL AND T2.col IS NULL) 标准。现在我可以添加我的主键约束了。 谢谢,事实证明这比其他解决方案快得多。我在 1 小时后放弃了一些版本,这几乎是立即完成的 对我很有帮助的解决方案,因为我可以在执行之前目视检查删除列表。【参考方案2】:

如果没有其他唯一标识,可以使用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 是其中一个重要原因。

【讨论】:

我的表中没有任何名为 ctid 的字段,您能解释一下您从哪里得到的吗?谢谢 ctid 是一个隐藏字段。当您检索表定义时,它不会显示。它是一种内部行号。 where not exists 将删除没有重复的行。应该是where exists (select 1` @GordonLinoff - 感谢您的澄清。我知道这是题外话;这就是我问题前缀中OT: 的含义;) 在我的小桌子上我做了:select ctid, * from tablectid 表示为 (0,1)、(0,2) 等。所以我能够对重复行执行简单的删除语句:delete from table where ctid = '(0,1)'【参考方案3】:

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

In-order sequence generation How do I decompose ctid into page and row numbers?
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。见:

Find records where join doesn't exist

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

重要区别:这些其他查询将 NULL 值视为不等于,而 GROUP BY(或DISTINCTDISTINCT ON ()) 将 NULL 值视为相等。对于定义为NOT NULL 的列无关紧要。否则,根据您对“重复”的定义,您将需要一种方法或另一种方法。 使用IS NOT DISTINCT FROM 比较值(可能会排除某些索引)。

免责声明:

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

相关:

How do I (or can I) SELECT DISTINCT on multiple columns?

How to use the physical location of rows (ROWID) in a DELETE statement

旁白:

DELETE 语句的目标不能是 CTE,只能是基础表。这是 SQL Server 的溢出效应——你的整个方法也是如此。

【讨论】:

我喜欢这个解决方案,因为它非常简洁。关于我在下面发布的解决方案的性能有什么想法吗? ***.com/a/46775289/968244 我实际上能够测试它。我有一个大约有 350k 行的表,它在 7 列中有 39 个重复项,没有索引。我首先尝试了GROUP BY 解决方案,它花费了 30 多秒,所以我杀了它。然后我尝试了USING 解决方案,它在大约 16 秒内完成。 @isapir:就像我在 2014 年提到的那样:NOT IN 语法很短,但EXISTS 更快。 (与使用 USING 子句的完全有效的查询相同。)但是有一个细微的差别。我在上面添加了一条注释。 酷。感谢您的澄清。【参考方案4】:

这是我想出的,使用group by

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

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

【讨论】:

我的表中没有 id,这是 netezza 数据库,它们没有像 sql server 这样的自动递增数字 是否有另一列唯一标识行? HAVING 子句对于这个查询来说是噪音。在 any 情况下,每个现有 id 的计数 >= 1。你可以删除它。【参考方案5】:

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

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));

【讨论】:

【参考方案6】:

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

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

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;

【讨论】:

【参考方案7】:

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

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);

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

复制表格前请删除'row_n'列

【讨论】:

【参考方案8】:

如果您希望每一行都有一个唯一标识符,您只需添加一个(序列号或 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;

【讨论】:

Netezza 不支持主键或唯一键约束 没有。【参考方案9】:

来自文档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 字段。

【讨论】:

【参考方案10】:

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

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

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

【讨论】:

以上是关于如何删除没有唯一标识符的重复行的主要内容,如果未能解决你的问题,请参考以下文章

数据集中的唯一标识符,索引问题

如何更新没有任何数据的行以唯一标识该行?

在django中生成唯一的字母数字标识符[重复]

查找唯一标识符重复的每个字段的最大序列号

如何获取Android唯一标识

SQL UPSERT QUERY W/基于 3 个字段的唯一重复行 (C# VisStudio)