如何删除重复的条目?
我必须添加一个唯一的约束到现有的表。 这很好,除了表已经有数百万行了,许多行违反了我需要添加的唯一约束。
什么是删除违规行最快的方法? 我有一个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)