删除PostgreSQL中的重复记录


113

我在PostgreSQL 8.3.8数据库中有一个表,该表上没有键/约束,并且有多个行,它们的值完全相同。

我想删除所有重复项,并且每行仅保留1个副本。

特别是有一列(称为“密钥”)可用于标识重复项(即,每个不同的“密钥”应该只存在一个条目)。

我怎样才能做到这一点?(最好是使用单个SQL命令)在这种情况下,速度不是问题(只有几行)。

Answers:


80
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

20
不要使用它,它太慢了!
帕维尔Malisak

5
尽管此解决方案确实有效,但@rapimo的以下解决方案执行速度更快。我相信这与这里的内部select语句被执行N次(针对dupes表中的所有N行)有关,而不是与其他解决方案中的分组有关。
大卫

对于大型表(几百万条记录),该表实际上适合内存,与@rapimo的解决方案不同。因此,在这些情况下,这是更快的方法(不交换)。
吉尔,

1
添加说明:之所以起作用,是因为ctid是特殊的postgres列,指示行的物理位置。即使表不具有唯一ID,也可以将其用作唯一ID。postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

194

更快的解决方案是

DELETE FROM dups a USING (
      SELECT MIN(ctid) as ctid, key
        FROM dups 
        GROUP BY key HAVING COUNT(*) > 1
      ) b
      WHERE a.key = b.key 
      AND a.ctid <> b.ctid

20
为什么它比a_horse_with_no_name的解决方案更快?
罗伯托

3
因为这样做只运行2个查询,所以速度更快。首先选择所有重复项,然后从表中删除所有项。@a_horse_with_no_name进行的查询将查询表中每个项目是否与其他项匹配。
风神

5
是什么ctid
techkuz

6
来自docs:ctid。行版本在其表中的物理位置。请注意,尽管可以使用ctid很快找到行版本,但是每次通过VACUUM FULL更新或移动行时,其ctid都会更改。因此,ctid不能用作长期行标识符。
萨伊姆

1
当重复行多于2个时,似乎这种方法不起作用,因为它一次仅删除一个重复项。
Frankie Drake

74

这是快速而简洁的:

DELETE FROM dupes T1
    USING   dupes T2
WHERE   T1.ctid < T2.ctid  -- delete the older versions
    AND T1.key  = T2.key;  -- add more columns if needed

另请参阅我的答案,如何删除包含唯一标识符的重复行,其中包括更多信息。


ct代表什么?计数?
techkuz

4
@trthhrtz ctid指向表中记录的物理位置。与我当时在注释中写的相反,使用小于运算符不一定指向较旧的版本,因为ct可以环绕,而ctid较低的值实际上可能是较新的。
isapir

1
仅供参考,我尝试了此解决方案,并在等待15分钟后终止了该解决方案。尝试了rapimo的解决方案,并在大约10秒内完成了操作(删除了约700,000行)。
Patrick

@Patrick无法想象您的数据库是否没有唯一标识符,因为rapimo的答案在这种情况下不起作用。
stucash

@ isapir我只是很好奇,上面的答案,他们是保持旧记录正确,因为他们选择了min(ctid)吗?而您正在保留更新的?谢谢!
stucash

17

我尝试了这个:

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

由Postgres Wiki提供:

https://wiki.postgresql.org/wiki/Deleting_duplicates


与@rapimo的答案和可接受的答案(@a_horse_with_no_name)相比,您对性能有任何想法吗?
tuxayo

3
如果像问题所述那样,所有列都相同(id包括在内),则此列将不起作用。
ibizaman '17

该查询将删除原始副本和重复副本。问题是要保留至少一排。
pyBomb

@pyBomb错误,它将保留列1 id... 3重复的第一个
Jeff

从postgresql 12开始,这是BY FAR最快的解决方案(针对3亿行)。我刚刚测试了此问题中提出的所有内容,包括已接受的答案,并且这种“官方”解决方案实际上是最快的,并且可以满足OP(和我的)的所有要求
Jeff

7

我将使用一个临时表:

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;

然后,删除tab并重命名tab_temptab


9
这种方法不考虑触发器,索引和统计信息。当然,您可以添加它们,但是它也增加了很多工作。
乔丹

1
并非每个人都需要。这种方法非常快速,并且在没有索引的200k电子邮件(varchar 250)中比其他方法更有效。
谢尔盖·泰尔谢夫斯基

1
完整代码:DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
Eric Burel

7

我必须创建自己的版本。@a_horse_with_no_name编写的版本在我的表(2100万行)上太慢了。@rapimo根本不会删除公仔。

这是我在PostgreSQL 9.5上使用的

DELETE FROM your_table
WHERE ctid IN (
  SELECT unnest(array_remove(all_ctids, actid))
  FROM (
         SELECT
           min(b.ctid)     AS actid,
           array_agg(ctid) AS all_ctids
         FROM your_table b
         GROUP BY key1, key2, key3, key4
         HAVING count(*) > 1) c);

1

另一种方法(仅当您id在表中有任何唯一字段时才起作用),按列查找所有唯一ID,并删除不在唯一列表中的其他ID

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);

问题是,在我的问题中,表格没有唯一的ID;“重复项”是多行,所有列上的值都完全相同。
安德烈Morujão

是的,我添加了一些笔记
Zaytsev Dmitry

1

怎么样:

与
  u AS(SELECT DISTINCT * FROM your_table),
  x AS(从your_table中删除)
插入your_table SELECT * FROM u;

我一直在担心执行顺序,是否会在SELECT DISTINCT之前发生DELETE,但对我来说很好。并且具有不需要任何有关表结构的知识的额外好处。


唯一的缺点是,如果您的数据类型不支持相等性(例如json),则将无法使用。
a_horse_with_no_name

0

这对我来说很好。我有一个表,术语,其中包含重复的值。运行查询以使用所有重复的行填充临时表。然后,我在临时表中运行带有这些id的delete语句。value是包含重复项的列。

        CREATE TEMP TABLE dupids AS
        select id from (
                    select value, id, row_number() 
over (partition by value order by value) 
    as rownum from terms
                  ) tmp
                  where rownum >= 2;

delete from [table] where id in (select id from dupids)

0

这是使用的解决方案PARTITION BY

DELETE FROM dups
USING (
  SELECT
    ctid,
    (ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])) AS is_duplicate
  FROM dups 
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.