如何删除重复的条目?

时间:2009-11-17 02:25:21

标签: sql postgresql duplicate-removal unique-constraint sql-delete

我必须为现有表添加唯一约束。这很好,除了表已经有数百万行,并且许多行违反了我需要添加的唯一约束。

删除有问题的行的最快方法是什么?我有一个SQL语句,它找到重复项并删除它们,但它需要永远运行。有没有其他方法可以解决这个问题?也许备份表,然后在添加约束后恢复?

16 个答案:

答案 0 :(得分:173)

其中一些方法看起来有点复杂,我通常这样做:

给定表table,想要在(field1,field2)上使其唯一,并保持行与max field3:

DELETE FROM table USING table alias 
  WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
    table.max_field < alias.max_field

例如,我有一个表user_accounts,我想在电子邮件中添加一个唯一约束,但我有一些重复。还要说我想保留最近创建的一个(重复项中的最大ID)。

DELETE FROM user_accounts USING user_accounts ua2
  WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
  • 注意 - USING不是标准SQL,它是PostgreSQL扩展(但非常有用),但原始问题特别提到了PostgreSQL。

答案 1 :(得分:99)

例如你可以:

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;

答案 2 :(得分:25)

除了创建新表之外,您还可以在截断它之后将唯一行重新插入到同一个表中。在一次交易中全部完成。 (可选)您可以使用ON COMMIT DROP自动删除事务结束时的临时表。见下文。

此方法仅在需要从整个表中删除大量行时才有用。对于一些重复项,请使用普通DELETE

你提到了数百万行。要使操作快速,您需要为会话分配足够的temporary buffers。在当前会话中使用任何临时缓冲区之前,必须先调整设置。找出你桌子的大小:

SELECT pg_size_pretty(pg_relation_size('tbl'));

相应地设置temp_buffers。慷慨解囊,因为内存中的表示需要更多的RAM。

SET temp_buffers = 200MB;    -- example value

BEGIN;

-- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit
CREATE TEMPORARY TABLE t_tmp AS  -- retain temp table after commit
SELECT DISTINCT * FROM tbl;  -- DISTINCT folds duplicates

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;
-- ORDER BY id; -- optionally "cluster" data while being at it.

COMMIT;

此方法优于创建新表,如果依赖对象存在。引用该表的视图,索引,外键或其他对象。 TRUNCATE无论如何都会让你开始使用干净的平板(背景中的新文件),并且 比使用大表DELETE FROM tbl更快 DELETE实际上可以更快小桌子。)

对于大表,定期更快删除索引和外键,重新填充表并重新创建这些对象。至于fk约束,你必须确定新数据当然是有效的,否则你会在尝试创建fk时遇到异常。

请注意,TRUNCATE需要比DELETE更具侵略性的锁定。对于具有大量并发负载的表,这可能是一个问题。

如果TRUNCATE不是一个选项,或者通常用于中小型表,则会采用类似的技术data-modifying CTE(Postgres 9.1 + ):

WITH del AS (DELETE FROM tbl RETURNING *)
INSERT INTO tbl
SELECT DISTINCT * FROM del;
-- ORDER BY id; -- optionally "cluster" data while being at it.

大表格较慢,因为TRUNCATE更快。但对于小桌子来说可能更快(也更简单!)。

如果你根本没有依赖对象,你可以创建一个新表并删除旧表,但是你很难获得这种通用方法。

对于不适合可用内存的非常大的表格,创建表格会快得多。你必须权衡这与可能的依赖对象的麻烦/开销。

答案 3 :(得分:20)

您可以使用oid或ctid,它通常是表格中的“不可见”列:

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);

答案 4 :(得分:19)

PostgreSQL窗口函数可以解决这个问题。

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

请参阅 Deleting duplicates

答案 5 :(得分:8)

删除重复项的通用查询:

DELETE FROM table_name
WHERE ctid NOT IN (
  SELECT max(ctid) FROM table_name
  GROUP BY column1, [column 2, ...]
);

ctid是可用于每个表的特殊列,但除非特别提及,否则不可见。 ctid列值对于表中的每一行都被视为唯一。

答案 6 :(得分:7)

来自an old postgresql.org mailing list

create table test ( a text, b text );

唯一值

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

重复值

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

再多一次双重

insert into test values ( 'x', 'y');

select oid, a, b from test;

选择重复行

select o.oid, o.a, o.b from test o
    where exists ( select 'x'
                   from test i
                   where     i.a = o.a
                         and i.b = o.b
                         and i.oid < o.oid
                 );

删除重复的行

注意:PostgreSQL不支持别名 from子句中提到的表 删除。

delete from test
    where exists ( select 'x'
                   from test i
                   where     i.a = test.a
                         and i.b = test.b
                         and i.oid < test.oid
             );

答案 7 :(得分:4)

我刚刚成功地使用Erwin Brandstetter's answer来删除连接表中的重复项(缺少自己的主ID的表),但发现有一个重要的警告。

包含ON COMMIT DROP表示临时表将在事务结束时被删除。对我来说,这意味着当我插入临时表时不再可用

我刚做CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;,一切正常。

临时表确实在会话结束时被删除。

答案 8 :(得分:3)

DELETE FROM table
  WHERE something NOT IN
    (SELECT     MAX(s.something)
      FROM      table As s
      GROUP BY  s.this_thing, s.that_thing);

答案 9 :(得分:3)

首先,您需要决定要保留哪些“重复”。如果所有列都相同,那么,您可以删除其中的任何列...但是,您可能只想保留最新的或其他一些标准?

最快的方法取决于您对上述问题的回答,以及表格中重复次数的百分比。如果丢弃50%的行,最好不要执行CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;,如果删除1%的行,则使用DELETE会更好。

同样对于这样的维护操作,将work_mem设置为RAM的一大块通常是好的:运行EXPLAIN,检查排序/哈希的数量N,并将work_mem设置为RAM / 2 / N使用大量的RAM;这对速度有好处。只要你只有一个并发连接......

答案 10 :(得分:3)

此函数在不删除索引的情况下删除重复项,并将其复制到任何表中。

用法:select remove_duplicates('mytable');

---
--- remove_duplicates(tablename) removes duplicate records from a table (convert from set to unique set)
---
CREATE OR REPLACE FUNCTION remove_duplicates(text) RETURNS void AS $$
DECLARE
  tablename ALIAS FOR $1;
BEGIN
  EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT * FROM ' || tablename || ');';
  EXECUTE 'DELETE FROM ' || tablename || ';';
  EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
  EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
  RETURN;
END;
$$ LANGUAGE plpgsql;

答案 11 :(得分:3)

如果您只有一个或几个重复的条目,并且它们确实重复(即它们出现两次),您可以使用&#34; hidden&#34;如上所述,ctid列与LIMIT

一起
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);

这将仅删除所选行中的第一行。

答案 12 :(得分:1)

我正在使用PostgreSQL 8.4。当我运行提议的代码时,我发现它不是 实际上删除重复。在运行一些测试时,我发现添加了 “DISTINCT ON(duplicate_column_name)”和“ORDER BY duplicate_column_name”完成了这一操作。我不是SQL大师,我在PostgreSQL 8.4 SELECT ... DISTINCT doc。

中找到了这个
CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$
DECLARE
  tablename ALIAS FOR $1;
  duplicate_column ALIAS FOR $2;
BEGIN
  EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);';
  EXECUTE 'DELETE FROM ' || tablename || ';';
  EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
  EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
  RETURN;
END;
$$ LANGUAGE plpgsql;

答案 13 :(得分:1)

这非常好用,非常快:

CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;

答案 14 :(得分:1)

CREATE TABLE test (col text);
INSERT INTO test VALUES
 ('1'),
 ('2'), ('2'),
 ('3'),
 ('4'), ('4'),
 ('5'),
 ('6'), ('6');
DELETE FROM test
 WHERE ctid in (
   SELECT t.ctid FROM (
     SELECT row_number() over (
               partition BY col
               ORDER BY col
               ) AS rnum,
            ctid FROM test
       ORDER BY col
     ) t
    WHERE t.rnum >1);

答案 15 :(得分:1)

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的行。该模式取自postgres wiki

使用CTE,您可以通过此

实现上述更具可读性的版本
WITH duplicate_ids as (
    SELECT id, rnum 
    FROM num_of_rows
    WHERE rnum > 1
),
num_of_rows as (
    SELECT id, 
        ROW_NUMBER() over (partition BY column1, 
                                        column2, 
                                        column3 ORDER BY id) AS rnum
        FROM tablename
)
DELETE FROM tablename 
WHERE id IN (SELECT id from duplicate_ids)