删除PostgreSQL中的重复记录

And*_*jão 94 sql postgresql duplicates

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

我想删除所有重复项,每行只保留1份.

特别是有一列(称为"密钥")可用于识别重复(即每个不同的"密钥"应该只存在一个条目).

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

rap*_*imo 151

更快的解决方案是

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
Run Code Online (Sandbox Code Playgroud)

  • 为什么它比a_horse_with_no_name的解决方案更快? (17认同)
  • 这更快,因为它只运行 2 个查询。第一个选择所有重复项,然后一个从表中删除所有项目。@a_horse_with_no_name 的查询执行查询以查看它是否与表中的每个单项匹配。 (7认同)
  • 什么是`ctid`? (7认同)
  • @Daria,你错了。该查询删除所有 ctid 不是每个键的 min(citid) 的重复记录。一个简单的测试就可以证明这一点。`创建表 t_location (国家文本,城市文本); 插入 t_location 值 ('Country', 'City1'), ('Country','City2'),('Country','City3'); -- 重复几次从 t_location a 中删除,使用 ( select min(ctid) as ctid, city from t_location group by cityhaving count(*) &gt; 1 ) b where a.city = b.city and a.ctid &lt;&gt; b. CTID;select * from t_location 按城市顺序;--只有3条记录` (4认同)
  • 来自docs:ctid。表中行版本的物理位置。请注意,尽管可以使用ctid很快找到行版本,但是每次通过VACUUM FULL更新或移动行时,其ctid都会更改。因此,ctid不能用作长期行标识符。 (3认同)
  • 当有超过 2 个重复行时,这似乎不起作用,因为它一次只删除一个重复行。 (2认同)

a_h*_*ame 69

DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);
Run Code Online (Sandbox Code Playgroud)

  • 不要用它,太慢了! (11认同)
  • 虽然这个解决方案肯定有效,但@rapimo的[下面的解决方案](/sf/answers/907417871/)执行得更快.我相信这与内部select语句有关,这里执行N次(对于dupes表中的所有N行)而不是在另一个解决方案中进行的分组. (4认同)
  • 添加解释:它之所以有效,是因为 ctid 是一个特殊的 postgres 列,指示行的物理位置。即使您的表没有唯一 ID,您也可以使用它作为唯一 ID。https://www.postgresql.org/docs/8.2/ddl-system-columns.html (2认同)

isa*_*pir 55

这快速而简洁:

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
Run Code Online (Sandbox Code Playgroud)

另请参阅我的答案如何删除没有包含更多信息的唯一标识符的重复行.

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

Rad*_*iel 14

我试过这个:

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);
Run Code Online (Sandbox Code Playgroud)

由Postgres维基提供:

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

  • 如问题状态所述,如果_all_列相同,且包含`id',则此列将不起作用。 (2认同)

Erw*_*ter 12

EXISTS 对于大多数数据分布来说,它很简单并且是最快的:

DELETE FROM dupes d
WHERE  EXISTS (
   SELECT FROM dupes
   WHERE  key = d.key
   AND    ctid < d.ctid
   );
Run Code Online (Sandbox Code Playgroud)

从每组重复行(由相同的定义key)中,这将保留具有最小值的一行ctid

结果与a_horse 当前接受的答案相同。只是更快,因为EXISTS一旦找到第一个违规行就可以停止评估,而替代方案min()必须考虑每组的所有行来计算最小值。这个问题与速度无关,但为什么不考虑呢?

您可能希望在清理后添加UNIQUE约束,以防止重复项重新出现:

ALTER TABLE dupes ADD CONSTRAINT constraint_name_here UNIQUE (key);
Run Code Online (Sandbox Code Playgroud)

关于系统栏ctid

如果表中有任何其他列定义的UNIQUE NOT NULL列(如 a PRIMARY KEY),那么无论如何,请使用它而不是ctid.

如果key可以,NULL而您也只想要其中之一,请使用IS NOT DISTINCT FROM代替=。看:

由于速度较慢,您可以改为按原样运行上述查询,另外

ALTER TABLE dupes ADD CONSTRAINT constraint_name_here UNIQUE (key);
Run Code Online (Sandbox Code Playgroud)

并考虑:

对于小表,索引通常对性能没有帮助。我们不需要再看下去了。

对于大表少量重复,现有的索引(key)可以提供帮助(很多)。

对于大多数重复项,索引可能会增加成本而不是收益,因为它必须同时保持最新。无论如何,在没有索引的情况下查找重复项会变得更快,因为数量太多,EXISTS只需要找到一个即可。但是,如果您负担得起,请考虑一种完全不同的方法(即并发访问允许):将少数幸存的行写入新表。这也消除了过程中的表(和索引)膨胀。看:


exp*_*ert 6

我不得不创建自己的版本.@a_horse_with_no_name编写的版本在我的桌子上太慢了(21M行).并且@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);
Run Code Online (Sandbox Code Playgroud)


Pab*_*ruz 5

我将使用一个临时表:

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;
Run Code Online (Sandbox Code Playgroud)

然后,删除tab并重命名tab_temptab

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

Zay*_*try 5

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

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);
Run Code Online (Sandbox Code Playgroud)