如何删除重复的条目?

gjr*_*ber 93 sql postgresql duplicate-removal unique-constraint sql-delete

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

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

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

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

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

  • @Tim你能更好地解释postgresql中`USING`做了什么吗? (5认同)
  • postgres的第二种方法非常快!谢谢. (4认同)
  • 这是迄今为止最好的答案.即使您的表中没有用于id比较的串行列,也值得临时添加一个使用这种简单方法. (3认同)
  • 我刚刚检查。答案是肯定的。使用小于号(&lt;)仅保留最大ID,而大于号(&gt;)仅保留最小ID,删除其余部分。 (2认同)

jus*_*ody 101

例如,您可以:

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
Run Code Online (Sandbox Code Playgroud)

  • 更容易输入:`CREATE TABLE tmp AS SELECT ...;`.那么你甚至不需要弄清楚`tmp`的布局是什么.:) (36认同)
  • DISTINCT ON(a,b,c):http://www.postgresql.org/docs/8.2/interactive/sql-select.html (10认同)
  • 由于几个原因,这个答案实际上并不是很好.@Randal命名一个.在大多数情况下,特别是如果你有索引,约束,视图等依赖对象,优越的方法是使用实​​际的[TEMPORARY TABLE](http://www.postgresql.org/docs/current/interactive/sql- createtable.html),[TRUNCATE](http://www.postgresql.org/docs/current/interactive/sql-truncate.html)原文并重新插入数据. (9认同)
  • 你对索引是正确的.删除和重新创建要快得多.但是其他依赖对象会破坏或阻止完全丢弃表格 - 在制作副本之后OP会发现* - 对于"最快的方法"来说这么多.不过,你是对的还是正确的.这是没有根据的,因为这不是一个坏的答案.这不是那么好.您可以添加一些关于索引或依赖对象的指针或手册的链接,就像您在评论中所做的那样或*任何*类型的解释.我想我对人们投票的方式感到沮丧.删除了downvote. (7认同)
  • 你可以让它与列组不同.也许"SELECT DISTINCT(ta,tb,tc),*FROM t"? (2认同)

Erw*_*ter 25

您可以在截断后将唯一行重新插入到同一个表中,而不是创建新表.在一次交易中完成所有工作.(可选)您可以使用自动删除事务结束时的临时表ON COMMIT DROP.见下文.

这种方法仅在需要从表中删除大量行时才有用.只需几个重复,使用普通DELETE.

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

SELECT pg_size_pretty(pg_relation_size('tbl'));
Run Code Online (Sandbox Code Playgroud)

相应地设定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;
Run Code Online (Sandbox Code Playgroud)

如果存在依赖对象,此方法优于创建新表.引用该表的视图,索引,外键或其他对象.TRUNCATE让你用干净的石板开始呢(在后台新的文件),并且是快于DELETE FROM tbl大表(DELETE实际上可以用小桌子更快).

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

请注意,TRUNCATE需要更积极的锁定DELETE.对于具有大量并发负载的表,这可能是一个问题.

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

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

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

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

  • 我也用这种方法.但是,它可能是个人的,但我的临时表已被删除,并且在截断后不可用...如果临时表已成功创建并且可用,请小心执行这些步骤. (2认同)
  • 使用 USING 的解决方案在包含 1400 万条记录的表上花费了 3 个多小时。这个使用 temp_buffers 的解决方案花了 13 分钟。谢谢。 (2认同)

Jan*_*rek 20

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

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);
Run Code Online (Sandbox Code Playgroud)

  • 为了删除*in**,[`NOT EXISTS`应该快得多](http://stackoverflow.com/a/17109431/939860):`DELETE FROM tbl t WHERE EXISTS(SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid> t.ctid)` - 或使用任何其他列或列集进行排序以选择幸存者. (4认同)

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

请参阅删除重复项.


naX*_*aXa 8

删除重复项的通用查询:

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

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


Bha*_*ani 7

旧的postgresql.org邮件列表:

create table test ( a text, b text );
Run Code Online (Sandbox Code Playgroud)

独特的价值观

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
Run Code Online (Sandbox Code Playgroud)

重复值

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );
Run Code Online (Sandbox Code Playgroud)

再多一次双重复制

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

select oid, a, b from test;
Run Code Online (Sandbox Code Playgroud)

选择重复的行

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

删除重复的行

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