从小表中删除重复行

Posted

技术标签:

【中文标题】从小表中删除重复行【英文标题】:Delete duplicate rows from small table 【发布时间】:2011-09-28 20:39:39 【问题描述】:

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

我想删除所有重复项并只保留每行的 1 个副本。

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

我该怎么做? (理想情况下,使用单个 SQL 命令。) 在这种情况下,速度不是问题(只有几行)。

【问题讨论】:

【参考方案1】:

更快的解决方案是

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

【讨论】:

为什么比a_horse_with_no_name的方案快? 这更快,因为它只运行 2 个查询。第一个选择所有重复项,然后一个从表中删除所有项目。 @a_horse_with_no_name 的查询执行查询以查看它是否与表中的每个项目匹配。 什么是ctid 来自文档:ctid.行版本在其表中的物理位置。请注意,尽管 ctid 可用于非常快速地定位行版本,但行的 ctid 会在每次被 VACUUM FULL 更新或移动时发生变化。因此 ctid 作为长期行标识符是没有用的。 当重复行超过 2 行时,这似乎不起作用,因为它一次只删除一个重复。【参考方案2】:
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

【讨论】:

别用了,太慢了! 虽然这个解决方案确实有效,但 @rapimo 的 solution below 执行得更快。我相信这与这里的内部选择语句执行 N 次(对于 dupes 表中的所有 N 行)有关,而不是与其他解决方案中正在进行的分组有关。 对于巨大的表(几百万条记录),这个实际上适合内存,与@rapimo 的解决方案不同。所以在这些情况下,这是更快的一个(没有交换)。 补充说明:它起作用是因为 ctid 是一个特殊的 postgres 列,指示行的物理位置。即使您的表没有唯一 ID,您也可以将其用作唯一 ID。 postgresql.org/docs/8.2/ddl-system-columns.html @PawełMalisak 我在运行后阅读了它。【参考方案3】:

这既快速又简洁:

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

另请参阅我在 How to delete duplicate rows without unique identifier 的回答,其中包含更多信息。

【讨论】:

ct 代表什么?数? @trthhrtz ctid 指向表中记录的物理位置。与我当时在评论中写的相反,使用小于运算符不一定指向旧版本,因为 ct 可以环绕,并且具有较低 ctid 的值实际上可能更新。 仅供参考,我尝试了这个解决方案,并在等待 15 分钟后中止了它。尝试了 rapimo 的解决方案,它在大约 10 秒内完成(删除了约 700,000 行)。 @Patrick 无法想象您的数据库是否没有唯一标识符,因为 rapimo 的答案在这种情况下不起作用。 @isapir 我只是好奇,上面的答案,他们在选择min(ctid) 时保留了旧记录吗?而你的保留较新的?谢谢!【参考方案4】:

我试过了:

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)相比,您对性能有什么看法吗? 如果像问题所述,所有列相同,包括id,则此列将不起作用。 此查询将删除原始副本和副本。问题是关于保留至少一行。 @pyBomb 错误,它将保留第一个 id 其中 column1...3 重复 从 postgresql 12 开始,这是迄今为止最快的解决方案(针对 3 亿行)。我刚刚测试了这个问题中提出的所有内容,包括接受的答案,这个“官方”解决方案实际上是最快的,并且满足 OP(和我的)的所有要求【参考方案5】:

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

DELETE FROM dupes d
WHERE  EXISTS (
   SELECT FROM dupes
   WHERE  key = d.key
   AND    ctid < d.ctid
   );

从每组重复行(由相同的key 定义)中,这会保留ctid 最少的一行。

结果与currently accepted answer by a_horse 相同。只是更快,因为EXISTS 可以在找到第一个违规行后立即停止评估,而min() 的替代方案必须考虑所有 em> 每组的行数来计算最小值。 这个问题与速度无关,但为什么不考虑呢?

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

ALTER TABLE dupes ADD CONSTRAINT constraint_name_here UNIQUE (key);

关于系统专栏ctid

Is the system column “ctid” legitimate for identifying rows to delete?

如果在表中定义了任何其他列UNIQUE NOT NULL 列(如PRIMARY KEY),那么请务必使用它而不是ctid

如果key 可以是NULL,而您也只需要其中一个,请使用IS NOT DISTINCT FROM 而不是=。见:

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

因为这样比较慢,所以您可以按原样运行上述查询,并且另外

DELETE FROM dupes d
WHERE  key IS NULL
AND    EXISTS (
   SELECT FROM dupes
   WHERE  key IS NULL
   AND    ctid < d.ctid
   );

并考虑:

Create unique constraint with null columns

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

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

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

How to delete duplicate entries?

【讨论】:

【参考方案6】:

我会使用临时表:

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

然后,删除tab 并将tab_temp 重命名为tab

【讨论】:

这种方法不考虑触发器、索引和统计信息。当然你可以添加它们,但它也增加了很多工作。 不是每个人都需要它。这种方法非常快,并且在 20 万封没有索引的电子邮件 (varchar 250) 上比其他方法效果更好。 完整代码: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;【参考方案7】:

我必须创建自己的版本。 @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);

【讨论】:

【参考方案8】:

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

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

【讨论】:

问题是,在我的问题中,这些表没有唯一的 ID; “重复”是多行,所有列上的值完全相同。 对,我加了一些注释【参考方案9】:

怎么样:

和 u AS (SELECT DISTINCT * FROM your_table), x AS(从 your_table 中删除) INSERT INTO your_table SELECT * FROM u;

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

【讨论】:

唯一的缺点是,如果你有不支持相等的数据类型(例如json),这将不起作用。【参考方案10】:

这是using PARTITION BY 和virtual ctid column 的解决方案,它的作用类似于主键,至少在单个会话中:

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

子查询用于将所有行标记为重复行,这取决于它们是否共享相同的“键列”,但与行的“分区”中的“第一个”行是否共享相同的ctid共享相同的密钥。

换句话说,“第一”被定义为:

min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])

然后,is_duplicate 为 true 的所有行都被它们的ctid 删除。

来自文档,ctid represents(强调我的):

行版本在其表中的物理位置。请注意,虽然 ctid 可用于非常快速地定位行版本,但如果行的 ctid 被 VACUUM FULL 更新或移动,它会发生变化。因此 ctid 作为长期行标识符是无用的。应该使用主键来标识逻辑行。

【讨论】:

【参考方案11】:

Postgresql 有 windows 功能,你可以使用 rank() 来归档你的目标,示例:

WITH ranked as (
    SELECT
        id, column1,
        "rank" () OVER (
            PARTITION BY column1
            order by column1 asc
        ) AS r
    FROM
        table1
) 
delete from table1 t1
using ranked
where t1.id = ranked.id and ranked.r > 1

【讨论】:

【参考方案12】:

这对我来说效果很好。我有一个包含重复值的表,术语。运行查询以使用所有重复行填充临时表。然后我在临时表中使用这些 id 运行删除语句。 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)

【讨论】:

以上是关于从小表中删除重复行的主要内容,如果未能解决你的问题,请参考以下文章

如何从 SQL Server 中的表中删除重复行 [重复]

如何从没有ID的表中删除重复(重复)记录,行[重复]

Oracle 删除表中的重复行并使用另一个表中的值更新行

查询删除临时表中的重复行

在Oracle中从表中删除重复行

sql 删除表中的重复行