如何删除重复的条目?

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

什么是删除违规行最快的方法? 我有一个SQL语句,它find重复项并删除它们,但它是永远运行。 有没有另一种方法来解决这个问题? 也许备份表,然后在添加约束后恢复?

例如,您可以:

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

其中一些方法似乎有点复杂,我通常这样做:

给定表格,想在(field1,field2)上保持行最大字段3:

 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 ,并且我想添加一个唯一的电子邮件约束,但我有一些重复。 还要说,我想保留最近创build的一个(重复中的最大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。

除了创build新表格之外,还可以在截断后将重复的唯一行插入到同一个表格中。 一次交易就完成了 。 或者,您可以使用ON COMMIT DROP自动删除临时表。 见下文。

这种方法只在需要从整个表中删除大量行的情况下才有用。 对于一些重复,使用一个普通的DELETE

你提到了数百万行。 为了使操作更快,您希望为会话分配足够的临时缓冲区 。 在当前会话中使用临时缓冲区之前 ,必须调整该设置。 找出你的桌子的大小:

 SELECT pg_size_pretty(pg_relation_size('tbl')); 

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

 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; 

如果依赖对象存在,此方法可以优于创build新表。 引用表的视图,索引,外键或其他对象。 TRUNCATE让你从一个干净的石板开始(在后台创build新的文件),比DELETE FROM tbl大表( DELETE实际上可以用小表更快)。

对于大表,通常会更快地删除索引和外键,重新填充表并重新创build这些对象。 就fk约束而言,您必须确定新数据当然是有效的,否则在尝试创buildfk时会遇到exception。

请注意, TRUNCATE需要比DELETE更积极的locking。 对于负载较重的表,这可能是一个问题。

如果TRUNCATE不是一个选项,或者一般用于中小型表,那么在数据修改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在那里更快。 但是对于小桌子可能会更快(也更简单!)。

如果你根本没有任何依赖的对象,你可以创build一个新的表,并删除旧的表,但你很难获得这个普遍的方法。

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

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

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

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

请参阅删除重复项

通用查询删除重复项:

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

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

从旧的postgresql.org邮件列表 :

 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重复的行

 select o.oid, oa, ob from test o where exists ( select 'x' from test i where ia = oa and ib = ob and i.oid < o.oid ); 

删除重复的行

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

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

我刚刚使用Erwin Brandstetter的答案成功删除了连接表中的重复项(缺less自己的主ID),但发现有一个重要的警告。

包括ON COMMIT DROP意味着在事务结束时临时表将被丢弃。 对我来说,这意味着当我去插入它时,临时表已经不可用了!

我只是做了CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl; 一切正常。

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

此函数删除重复项而不删除索引,并将其复制到任何表中。

用法: select remove_duplicates('mytable');

 ---
 --- remove_duplicates(tablename)从表中删除重复的logging(从集合转换为唯一集合)
 ---
创build或replace函数remove_duplicates(text)返回void AS $$
宣布
  表名ALIAS $ 1;
开始
   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 ||  ';';
  返回;
结束;
 $$语言plpgsql;
 DELETE FROM table WHERE something NOT IN (SELECT MAX(s.something) FROM table As s GROUP BY s.this_thing, s.that_thing); 

如果您只有一个或几个重复的条目,并且它们确实是重复的 (即它们出现两次),则可以使用上面提出的“隐藏” ctid列和LIMIT

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

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

首先,你需要决定你将要保留哪些“重复”。 如果所有列都是平等的,那么可以删除其中的任何一个…但也许您只想保留最新的或其他标准?

最快的方法取决于你对上述问题的回答,也取决于表格上重复的百分比。 如果你扔掉50%的行,你最好做CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ; ,如果你删除1%的行,使用DELETE更好。

对于这样的维护操作,通常也可以将work_mem设置为RAM的一大块:运行EXPLAIN,检查sorts / hashes的数量N,并将work_mem设置为RAM / 2 / N。使用大量的RAM; 这对速度有好处。 只要你只有一个并发连接…

我正在使用PostgreSQL 8.4。 当我运行提议的代码时,我发现它实际上并没有删除重复项。 在运行一些testing时,我发现添加“DISTINCT ON(duplicate_column_name)”和“ORDER BY duplicate_column_name”的技巧。 我不是SQL大师,我在PostgreSQL 8.4 SELECT … DISTINCT文档中find了这个。

 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; 
 CREATE TABLE test (col text); INSERT INTO test VALUES ('1'); INSERT INTO test VALUES ('2'); INSERT INTO test VALUES ('2'); INSERT INTO test VALUES ('3'); INSERT INTO test VALUES ('4'); INSERT INTO test VALUES ('4'); INSERT INTO test VALUES ('5'); INSERT INTO test VALUES ('6'); INSERT INTO test VALUES ('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) 

这工作非常好,非常快:

 CREATE INDEX otherTable_idx ON otherTable( colName ); CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable; 
 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

通过使用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)