Sql 如何删除重复条目?

Sql 如何删除重复条目?,sql,postgresql,duplicate-removal,unique-constraint,sql-delete,Sql,Postgresql,Duplicate Removal,Unique Constraint,Sql Delete,我必须向现有表添加唯一约束。这很好,只是表已经有数百万行,而且许多行违反了我需要添加的唯一约束 删除有问题的行的最快方法是什么?我有一个SQL语句,可以找到重复项并将其删除,但它需要花费很长时间才能运行。还有别的办法解决这个问题吗?可能是备份表,然后在添加约束后恢复?例如,您可以: CREATE TABLE tmp ... INSERT INTO tmp SELECT DISTINCT * FROM t; DROP TABLE t; ALTER TABLE tmp RENAME TO t; 首

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

删除有问题的行的最快方法是什么?我有一个SQL语句,可以找到重复项并将其删除,但它需要花费很长时间才能运行。还有别的办法解决这个问题吗?可能是备份表,然后在添加约束后恢复?

例如,您可以:

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

首先,你需要决定你将保留哪些副本。如果所有列都相等,则可以删除其中任何一列。。。但也许你只想保留最新的标准,或者其他一些标准

最快的方法取决于您对上述问题的回答,以及表中重复项的百分比。若您丢弃了50%的行,那个么最好还是创建表。。。由于选择不同。。。从…起如果删除了1%的行,使用delete会更好


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

此函数将删除重复项而不删除索引,并对任何表执行此操作

用法:选择删除重复项'mytable'


我正在使用PostgreSQL 8.4。当我运行建议的代码时,我发现它不是 实际上是删除重复项。在运行一些测试时,我发现添加 重复列名称上的DISTINCT和按重复列名称排序的ORDER实现了这一技巧。我不是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;

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

给定的表,要在字段1、字段2上对其进行唯一化,并将行与最大字段3保持一致:

DELETE FROM table USING table alias 
  WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
    table.max_field < alias.max_field
注意-使用不是标准的SQL,它是一个PostgreSQL扩展,但非常有用,但最初的问题特别提到了PostgreSQL。
可以使用oid或ctid,这通常是表中不可见的列:

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

您也可以在截断同一个表后将唯一的行重新插入到该表中,而不是创建新表。在一次交易中完成这一切

这种方法只有在表中有很多行要删除时才有用。对于少量重复项,请使用普通删除

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

SELECT pg_size_pretty(pg_relation_size('tbl'));
将temp_缓冲区设置为至少高于该值一点

SET temp_buffers = 200MB;   -- example value

BEGIN;

CREATE TEMP TABLE t_tmp AS  -- retains temp for duration of session
SELECT DISTINCT * FROM tbl  -- DISTINCT folds duplicates
ORDER  BY id;               -- optionally "cluster" data

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;        -- retains order (implementation detail)

COMMIT;
如果存在依赖对象,此方法可能优于创建新表。视图、索引、外键或引用表的其他对象。不管怎样,这会让你从一张干净的白板开始在后台创建一个新文件,并且比使用大表从tbl中删除要快得多。实际上,使用小表删除要快得多

对于大型表,删除索引和外键FK、重新填充表和重新创建这些对象通常会更快。当然,就FK约束而言,您必须确保新数据有效,否则在尝试创建FK时会遇到异常

请注意,TRUNCATE需要比DELETE更严格的锁定。对于具有繁重并发负载的表来说,这可能是一个问题。但是,与完全放下并替换桌子相比,它的破坏性更小

如果TRUNCATE不是一个选项,或者通常用于中小型表,那么Postgres 9.1+也有类似的技术:

对于大型表,速度较慢,因为截断速度较快。但是可能更快更简单!小桌子

如果您根本没有依赖对象,您可以创建一个新表并删除旧表,但这种通用方法几乎没有任何好处

对于不适合可用RAM的非常大的表,创建新表的速度将大大加快。您必须将这一点与可能出现的故障/相关对象的开销进行权衡。

来源:

唯一值 重复值 再重复一次 选择重复行
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);

请参阅。

这项功能非常好,速度非常快:

CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
我刚刚成功地删除了联接表中的重复项—一个缺少主ID的表,但发现有一个重要的警告

include ON COMMIT DROP意味着临时表将在事务结束时被删除。对我来说,这意味着在我插入临时表时,它已经不可用了

我刚刚创建了临时表t_tmp,作为selectdistinct*fromtbl;一切都很顺利

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

如果
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);
只有一个或几个重复的条目,并且它们确实是重复的,也就是说,它们出现两次,您可以使用隐藏的ctid列,如上所述,以及限制:


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

删除重复行的通用查询:

DELETE FROM table_name
WHERE ctid NOT IN (
  SELECT max(ctid) FROM table_name
  GROUP BY column1, [column 2, ...]
);
列ctid是一个特殊的列,可用于每个表,但除非特别提及,否则不可见。ctid列值对于表中的每一行都是唯一的。请参阅以了解有关ctid的更多信息

按列删除重复项并保留id最低的行。模式取自

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


这就是我目前正在做的,但它需要很长的时间才能运行。如果表中的多行在某个列中具有相同的值,这不会失败吗?您能为这组列设置不同的值吗。可能选择DISTINCT t.a,t.b,t.c,*FROM t t?在a,b,c上选择DISTINCT:更容易键入:创建表tmp作为SELECT。。。;。然后,您甚至不需要弄清楚tmp的布局是什么:这个答案其实不是很好,原因有几个@兰德尔说了一个。在大多数情况下,尤其是当您有索引、约束、视图等依赖对象时,最好的方法是使用实际的原始数据并重新插入数据。关于索引,您是对的。删除和重新创建要快得多。但其他依赖对象会破坏或阻止表完全丢失-OP在复制后会发现这一点-这对于最快的方法来说太多了。不过,关于否决票,你是对的。这是没有根据的,因为这不是一个坏答案。只是没那么好。您可以添加一些关于索引或依赖对象的指针,或者添加指向手册的链接,就像您在注释或任何解释中所做的那样。我想我对人们的投票方式感到失望。删除了否决票。第二种方法在postgres上非常快!谢谢。@Tim你能更好地解释一下使用postgresql做什么吗?这是目前为止最好的答案。即使您的表中没有用于id比较的串行列,也值得临时添加一列以使用此简单方法。我刚刚检查过。答案是肯定的。使用小于只会留下最小id,而删除其余的。@Shane one可以使用:WHERE table1.ctidI也使用了这种方法。但是,它可能是personnal,但我的临时表已被删除,并且在截断后不可用。。。如果临时表已成功创建且可用,请小心执行这些步骤。@xlash:您可以检查是否存在临时表,并为临时表使用其他名称或重新使用现有名称。。我在回答中添加了一点。警告:小心@xlash的+1-我必须重新导入数据,因为截断后临时表不存在。正如Erwin所说,在截断表之前,确保它存在。见@codebykat'sanswer@JordanArseno:我切换到了一个没有启用提交删除的版本,这样错过我在一个事务中写入的部分的人就不会丢失数据。我添加了BEGIN/COMMIT以澄清一个事务。使用的解决方案在表上花费了3个多小时,记录数为1400万条。此带有临时缓冲区的解决方案耗时13分钟。谢谢。对于就地删除,:从tbl t中删除,如果存在,请从tbl t1中选择1,其中t1.dist_col=t.dist_col和t1.ctid>t.ctid-或者使用任何其他列或列集进行排序以选择幸存者。@ErwinBrandstetter,您提供的查询是否不存在?@John:它必须存在于此处。如下所示:删除所有行,其中任何其他行在dist_col中的值相同,但ctid更大。每组重复的唯一幸存者将是ctid最大的一个。如果你只有几个重复的行,最简单的解决方案。如果您知道重复的数量,则可以与一起使用。您的解释非常巧妙,但您缺少一点,在“创建表”中指定oid,然后仅访问oid else错误消息display@Kalanidhi感谢您对答案改进的意见,我会考虑这一点。如果“oid”给你一个错误,你可以使用系统列“ctid”。我知道它不能解决OP的问题,OP有数百万行中的多个重复项,但它可能会有所帮助。这必须为每个重复行运行一次。shekwi的答案只需运行一次。使用ctid而不是id,这实际上适用于完全重复的行。很好的解决方案。我必须为一张有10亿条记录的桌子做这件事。我在内部添加了一个WHERE-to-the-internal-SELECT,以便分块执行。这是唯一通用的答案!在没有自/笛卡尔联接的情况下工作。值得一提的是,正确指定GROUPBY子句是非常重要的——这应该是现在违反的“唯一性标准”,或者如果您希望密钥检测重复项。如果指定错误,它将无法正确工作我测试了它,它工作了;为了便于阅读,我把它格式化了。看起来 非常复杂,但需要一些解释。对于他/她自己的用例,如何更改此示例?
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
                 );
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
             );
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);
CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
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);
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
DELETE FROM table_name
WHERE ctid NOT IN (
  SELECT max(ctid) FROM table_name
  GROUP BY column1, [column 2, ...]
);
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);
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)