如何删除重复的行?


1284

从相当大的SQL Server表(即300,000+行)中删除重复的行的最佳方法是什么?

由于RowID身份字段的存在,这些行当然不会是完美的重复项。

MyTable

RowID int not null identity(1,1) primary key,
Col1 varchar(20) not null,
Col2 varchar(2048) not null,
Col3 tinyint not null

13
PostgreSQL用户快速阅读提示(很多,链接的频率):Pg不会将CTE术语公开为可更新的视图,因此您不能DELETE FROM直接使用CTE术语。参见stackoverflow.com/q/18439054/398670
Craig Ringer 2013年

@CraigRinger对于Sybase来说是相同的-我在这里收集了其余解决方案(对PG和其他解决方案也应有效:stackoverflow.com/q/19544489/1855801(只需用ROWID()RowID列替换函数,如果有的话)
maf-soft

12
只是为了在此添加警告。在运行任何重复数据删除过程时,请始终先仔细检查要删除的内容!这是意外删除良好数据的非常常见的领域之一。
杰夫·戴维斯

Answers:


1141

假设没有空,你GROUP BY的唯一列,并SELECTMIN (or MAX)RowId的为行,以保持。然后,只需删除所有没有行ID的内容:

DELETE FROM MyTable
LEFT OUTER JOIN (
   SELECT MIN(RowId) as RowId, Col1, Col2, Col3 
   FROM MyTable 
   GROUP BY Col1, Col2, Col3
) as KeepRows ON
   MyTable.RowId = KeepRows.RowId
WHERE
   KeepRows.RowId IS NULL

如果您使用的是GUID而不是整数,则可以替换

MIN(RowId)

CONVERT(uniqueidentifier, MIN(CONVERT(char(36), MyGuidColumn)))

327
这样也行吗?DELETE FROM MyTable WHERE RowId NOT IN (SELECT MIN(RowId) FROM MyTable GROUP BY Col1, Col2, Col3);
GeorgSchölly10年

10
@Andriy -在SQL Server LEFT JOIN是低效率的比NOT EXISTS sqlinthewild.co.za/index.php/2010/03/23/...该站点上也比较NOT INVS NOT EXISTSsqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in我认为3分中NOT EXISTS表现最好。尽管可以避免,但是这三个都将生成具有自连接的计划。
马丁·史密斯,

12
@马丁,@乔治:所以,我做了一个小测试。如下所述创建并填充了一个大表:sqlinthewild.co.za/index.php/2010/03/23/…然后生成了两个SELECT,一个使用LEFT JOIN + WHERE IS NULL技术,另一个使用NOT在一个。然后我继续执行计划,您猜怎么着?LEFT JOIN的查询成本为18%,NOT IN的查询成本为82%,这让我感到非常惊讶。我可能做了一些我不应该做的事情,反之亦然,如果是的话,我真的很想知道。
Andriy M

16
@GeorgSchölly提供了一个很好的答案。我在一个我的PHP错误创建重复行的表上使用了它。
菲利普·基恩斯

12
抱歉,为什么DELETE MyTable FROM MyTable语法正确?我DELETE此处看不到将表名放在选项之后的位置。对不起,如果这对其他人是显而易见的;我只是尝试学习SQL的新手。比它为什么起作用更重要:在其中包括表名之间有什么区别?
levininja

760

另一种可能的方式是

; 

--Ensure that any immediately preceding statement is terminated with a semicolon above
WITH cte
     AS (SELECT ROW_NUMBER() OVER (PARTITION BY Col1, Col2, Col3 
                                       ORDER BY ( SELECT 0)) RN
         FROM   #MyTable)
DELETE FROM cte
WHERE  RN > 1;

我在ORDER BY (SELECT 0)上面使用,因为在打平时保留哪一行是任意的。

为了保留最新的RowID顺序,例如,您可以使用ORDER BY RowID DESC

执行计划

执行计划通常比接受的答案更简单,更有效,因为它不需要自我连接。

执行计划

但是,并非总是如此。一种GROUP BY可能是首选解决方案的地方是优先选择散列聚合而不是流聚合的情况。

ROW_NUMBER解决方案将始终提供几乎相同的计划,而该GROUP BY策略则更为灵活。

执行计划

可能支持散列聚合方法的因素是

  • 分区列上没有有用的索引
  • 相对较少的组,每组中重复项相对较多

在第二种情况的极端版本​​中(如果每个组中很少有很多重复的组),还可以考虑简单地将行插入以保存到新表中,然后TRUNCATE对原始行进行-ing并将其复制回去,从而与删除行的比例很高。


28
如果我可以补充:可接受的答案不适用于使用的表uniqueidentifier。这个简单得多,并且可以在任何桌子上完美地工作。谢谢马丁。
BrunoLM

15
这是一个很棒的答案!当我在意识到其中存在重复项之前删除了旧PK时,它起作用了。+100
Mikael Eliasson

12
我建议在DBA.SE上问这个问题(然后用这个答案)。然后,我们可以将其添加到规范答案列表中
Nick Chammas 2012年

16
与接受的答案不同,这也适用于没有RowId要比较的键()的表。
vossad01 2013年

8
另一方面,此版本不适用于所有SQL Server版本
David

150

在Microsoft支持站点上有一篇很好的文章,关于删除重复项。这非常保守-他们让您按照单独的步骤进行所有操作-但在大型表上应该可以很好地工作。

我过去曾使用自联接来做到这一点,尽管它可能带有HAVING子句:

DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField 
AND dupes.secondDupField = fullTable.secondDupField 
AND dupes.uniqueField > fullTable.uniqueField

完善!我发现这是删除旧mariadb版本10.1.xx上重复行的最有效方法。谢谢!
醉M

更简单易懂!
Marc

98

以下查询对于删除重复的行很有用。本例中的表具有ID作为标识列和具有重复数据的列是Column1Column2并且Column3

DELETE FROM TableName
WHERE  ID NOT IN (SELECT MAX(ID)
                  FROM   TableName
                  GROUP  BY Column1,
                            Column2,
                            Column3
                  /*Even if ID is not null-able SQL Server treats MAX(ID) as potentially
                    nullable. Because of semantics of NOT IN (NULL) including the clause
                    below can simplify the plan*/
                  HAVING MAX(ID) IS NOT NULL) 

以下脚本显示使用GROUP BYHAVINGORDER BY在一个查询中,并返回带有重复列,其计结果。

SELECT YourColumnName,
       COUNT(*) TotalCount
FROM   YourTableName
GROUP  BY YourColumnName
HAVING COUNT(*) > 1
ORDER  BY COUNT(*) DESC 

1
第一个脚本“您无法在FROM子句中指定目标表'TableName'进行更新”的MySQL错误
D.Rosado 2012年

除了已经报告的错误D.Rosado,您的第一个查询也非常慢。相应的SELECT查询在我的设置中的执行时间比接受的答案长20倍。
parvus

8
@parvus-问题被标记为SQL Server而不是MySQL。语法在SQL Server中很好。同样,MySQL众所周知在优化子查询方面也很糟糕,例如,请参见此处。此答案在SQL Server中很好。实际上NOT IN通常表现要比更好OUTER JOIN ... NULL。我将向HAVING MAX(ID) IS NOT NULL查询中添加a ,尽管从语义上讲这不是必需的,因为那样可以改善此处
Martin Smith

2
在PostgreSQL 8.4中很好用。
2014年

63
delete t1
from table t1, table t2
where t1.columnA = t2.columnA
and t1.rowid>t2.rowid

Postgres:

delete
from table t1
using table t2
where t1.columnA = t2.columnA
and t1.rowid > t2.rowid

为什么要在SQL Server问题上发布Postgres解决方案?
Lankymart '16

2
@Lankymart因为postgres用户也来了。看这个答案的分数。
加百利

2
我已经在一些受欢迎的SQL问题中看到了这一点,例如在这里这里这里。OP得到了他的回答,其他所有人也得到了一些帮助。没问题恕我直言。
加百利

44
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 

1
我在Azure SQL DW上收到此消息:DELETE语句当前不支持FROM子句。
阿米特(Amit)

40

这将删除重复的行,但第一行除外

DELETE
FROM
    Mytable
WHERE
    RowID NOT IN (
        SELECT
            MIN(RowID)
        FROM
            Mytable
        GROUP BY
            Col1,
            Col2,
            Col3
    )

请参考(http://www.codeproject.com/Articles/157977/Remove-Duplicate-Rows-from-a-Table-in-SQL-Server


10
对于mysql,它将给出错误:错误代码:1093。您不能在FROM子句中指定目标表'Mytable'进行更新。但是此较小的更改将适用于mysql:从Mytable中删除行ID不在(从ID删除ID(从(从SELECT SELECT(MIN(RowID)AS作为ID的Mytable GROUP,按Col1,Col2,Col3)AS TEMP作为ID的))SELECT ID)
Ritesh

35

我希望CTE从SQL Server表中删除重复的行

强烈建议您遵循本文:: http://codaffection.com/sql-server-article/delete-duplicate-rows-in-sql-server/

通过保持原始

WITH CTE AS
(
SELECT *,ROW_NUMBER() OVER (PARTITION BY col1,col2,col3 ORDER BY col1,col2,col3) AS RN
FROM MyTable
)

DELETE FROM CTE WHERE RN<>1

不保留原始

WITH CTE AS
(SELECT *,R=RANK() OVER (ORDER BY col1,col2,col3)
FROM MyTable)
 
DELETE CTE
WHERE R IN (SELECT R FROM CTE GROUP BY R HAVING COUNT(*)>1)

24

要获取重复的行:

SELECT
name, email, COUNT(*)
FROM 
users
GROUP BY
name, email
HAVING COUNT(*) > 1

要删除重复的行:

DELETE users 
WHERE rowid NOT IN 
(SELECT MIN(rowid)
FROM users
GROUP BY name, email);      

对于MySQL用户,请注意,首先它必须是DELETE FROM,其次它将无法工作,因为您不能SELECT从与之相同的表中DELETE进行。在MySQL中,它开始爆炸了MySQL error 1093
伊尔·梅

23

快速删除所有重复行(对于小型表):

select  distinct * into t2 from t1;
delete from t1;
insert into t1 select *  from t2;
drop table t2;

3
请注意,该问题实际上指定了不完全相同的重复(由于行ID)。
Dennis Jaheruddin

21

我更喜欢内部连接的subquery \具有count(*)> 1个解决方案,因为我发现它更易于阅读,并且很容易变成SELECT语句以在运行之前验证要删除的内容。

--DELETE FROM table1 
--WHERE id IN ( 
     SELECT MIN(id) FROM table1 
     GROUP BY col1, col2, col3 
     -- could add a WHERE clause here to further filter
     HAVING count(*) > 1
--)

它不会删除内部查询中显示的所有记录。我们只需要删除重复项并保留原始内容。
桑迪

3
您只会根据select子句中的min(id)返回ID最低的那个。
詹姆斯·埃里科

2
取消注释查询的第一行,第二行和最后一行。
詹姆斯·埃里科

7
这不会清除所有重复项。如果您有3行重复,它将仅选择具有MIN(id)的行,并删除该行,剩下两行重复。
Chloe

2
尽管如此,我最终还是反复使用了该语句,这样它才能真正取得进展,而不是使连接超时或计算机进入睡眠状态。我对其进行MAX(id)了更改,以消除后面的重复项,然后将其添加LIMIT 1000000到内部查询中,从而不必扫描整个表。这表明进度比其他答案要快得多,而其他答案似乎要花上几个小时。将表修剪到可管理的大小后,即可完成其他查询。提示:确保col1 / col2 / col3具有分组依据的索引。
Chloe

17
SELECT  DISTINCT *
      INTO tempdb.dbo.tmpTable
FROM myTable

TRUNCATE TABLE myTable
INSERT INTO myTable SELECT * FROM tempdb.dbo.tmpTable
DROP TABLE tempdb.dbo.tmpTable

5
如果您有对myTable的外键引用,则无法进行截断。
Sameer Alibhai 2013年

15

我认为我会分享自己的解决方案,因为它可以在特殊情况下使用。我的情况是具有重复值的表没有外键(因为这些值是从另一个数据库重复的)。

begin transaction
-- create temp table with identical structure as source table
Select * Into #temp From tableName Where 1 = 2

-- insert distinct values into temp
insert into #temp 
select distinct * 
from  tableName

-- delete from source
delete from tableName 

-- insert into source from temp
insert into tableName 
select * 
from #temp

rollback transaction
-- if this works, change rollback to commit and execute again to keep you changes!!

PS:在进行此类工作时,我总是使用事务,这不仅可以确保一切都整体执行,而且可以让我进行测试而不会冒任何风险。但是当然,您还是应该备份以确保...


14

该查询为我显示了很好的性能:

DELETE tbl
FROM
    MyTable tbl
WHERE
    EXISTS (
        SELECT
            *
        FROM
            MyTable tbl2
        WHERE
            tbl2.SameValue = tbl.SameValue
        AND tbl.IdUniqueValue < tbl2.IdUniqueValue
    )

它在30分钟内从2M表中删除了1M行(重复次数为50%)


14

使用CTE。想法是加入一个或多个构成重复记录的列,然后删除任何您喜欢的列:

;with cte as (
    select 
        min(PrimaryKey) as PrimaryKey
        UniqueColumn1,
        UniqueColumn2
    from dbo.DuplicatesTable 
    group by
        UniqueColumn1, UniqueColumn1
    having count(*) > 1
)
delete d
from dbo.DuplicatesTable d 
inner join cte on 
    d.PrimaryKey > cte.PrimaryKey and
    d.UniqueColumn1 = cte.UniqueColumn1 and 
    d.UniqueColumn2 = cte.UniqueColumn2;

1
我认为您在JOIN中缺少AND。
贾斯汀·

13

此处粘贴的链接上可以找到另一个简单的解决方案。这个简单易懂,似乎对大多数类似问题都有效。虽然它适用于SQL Server,但是使用的概念是可以接受的。

以下是链接页面的相关部分:

考虑以下数据:

EMPLOYEE_ID ATTENDANCE_DATE
A001    2011-01-01
A001    2011-01-01
A002    2011-01-01
A002    2011-01-01
A002    2011-01-01
A003    2011-01-01

那么我们如何删除那些重复的数据呢?

首先,使用以下代码在该表中插入一个标识列:

ALTER TABLE dbo.ATTENDANCE ADD AUTOID INT IDENTITY(1,1)  

使用以下代码来解决它:

DELETE FROM dbo.ATTENDANCE WHERE AUTOID NOT IN (SELECT MIN(AUTOID) _
    FROM dbo.ATTENDANCE GROUP BY EMPLOYEE_ID,ATTENDANCE_DATE) 

1
“易掌握”,“似乎是有效的”,但没有什么方法包括在Word中。试想一下,链接失效,有什么用将被知道的方法容易把握和有效?请考虑将方法描述的必要部分添加到您的帖子中,否则,这不是答案。
Andriy M

此方法对于尚未定义身份的表很有用。通常,您需要摆脱重复项才能定义主键!
杰夫·戴维斯

@JeffDavis- ROW_NUMBER在这种情况下,该版本可以很好地工作,而无需在开始之前花费大量时间添加新列。
马丁·史密斯

12

这是另一篇有关删除重复项的好文章。

它讨论了为什么很难:“ SQL基于关系代数,并且关系代数中不会出现重复项,因为在集合中不允许重复项。

临时表解决方案,以及两个mysql示例。

将来,您将在数据库级别或从应用程序角度阻止它。我建议使用数据库级别,因为您的数据库应负责维护参照完整性,而开发人员只会引起问题;)


1
SQL基于多集合。但是,即使它是基于集合的,这两个元组(1,a)和(2,a)也不同。
安德鲁

12

哦没问题。使用临时表。如果您想要一个“不是很有效”的语句,可以使用:

DELETE FROM MyTable WHERE NOT RowID IN
    (SELECT 
        (SELECT TOP 1 RowID FROM MyTable mt2 
        WHERE mt2.Col1 = mt.Col1 
        AND mt2.Col2 = mt.Col2 
        AND mt2.Col3 = mt.Col3) 
    FROM MyTable mt)

基本上,对于表中的每一行,子选择都会找到与所考虑的行完全相似的所有行的顶部RowID。因此,您最终获得了代表“原始”非重复行的RowID列表。


11

我有一个需要保留非重复行的表。我不确定速度或效率。

DELETE FROM myTable WHERE RowID IN (
  SELECT MIN(RowID) AS IDNo FROM myTable
  GROUP BY Col1, Col2, Col3
  HAVING COUNT(*) = 2 )

7
假设最多有1个重复项。
马丁·史密斯

为什么不HAVING COUNT(*) > 1呢?
Philipp M

11

用这个

WITH tblTemp as
(
SELECT ROW_NUMBER() Over(PARTITION BY Name,Department ORDER BY Name)
   As RowNumber,* FROM <table_name>
)
DELETE FROM tblTemp where RowNumber >1

10

另一种方法是创建具有相同字段和唯一索引的新表。然后将所有数据从旧表移动到新表。SQL SERVER自动忽略(如果存在重复值,也可以选择做什么:忽略,中断或其他)重复值。因此,我们有相同的表,没有重复的行。如果您不希望唯一索引,则在传输数据之后可以将其删除

特别是对于较大的表,您可以使用DTS(SSIS包来导入/导出数据),以便将所有数据快速传输到新的唯一索引表中。对于700万行,只需几分钟。


9

通过使用以下查询,我们可以删除基于单列或多列的重复记录。下面的查询是基于两列删除的。表名是:testing和列名empno,empname

DELETE FROM testing WHERE empno not IN (SELECT empno FROM (SELECT empno, ROW_NUMBER() OVER (PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)
or empname not in
(select empname from (select empname,row_number() over(PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)

9
  1. 创建具有相同结构的新空白表

  2. 像这样执行查询

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) > 1
  3. 然后执行此查询

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) = 1


7

我会提到这种方法,并且它会有所帮助,并且可以在所有SQL服务器中使用:通常只有一个-两个重复项,并且Ids和重复项数是已知的。在这种情况下:

SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0

7

从应用程序级别(不幸的是)。我同意,防止重复的正确方法是通过使用唯一索引在数据库级别进行,但是在SQL Server 2005中,索引只能为900个字节,而我的varchar(2048)字段却把它弄糟了。

我不知道它的性能如何,但是我认为您可以编写一个触发器来强制执行此操作,即使您不能直接使用索引来执行它。就像是:

-- given a table stories(story_id int not null primary key, story varchar(max) not null)
CREATE TRIGGER prevent_plagiarism 
ON stories 
after INSERT, UPDATE 
AS 
    DECLARE @cnt AS INT 

    SELECT @cnt = Count(*) 
    FROM   stories 
           INNER JOIN inserted 
                   ON ( stories.story = inserted.story 
                        AND stories.story_id != inserted.story_id ) 

    IF @cnt > 0 
      BEGIN 
          RAISERROR('plagiarism detected',16,1) 

          ROLLBACK TRANSACTION 
      END 

另外,varchar(2048)对我来说听起来像是可疑的(生活中有些东西是2048字节,但这很不常见)。真的不是varchar(max)吗?



7
DELETE
FROM
    table_name T1
WHERE
    rowid > (
        SELECT
            min(rowid)
        FROM
            table_name T2
        WHERE
            T1.column_name = T2.column_name
    );

嗨,蒂娜,您已经在删除注释后错过了表爱丽丝的名字T1,否则它将导致语法异常。
Nagaraj M'17年

6
CREATE TABLE car(Id int identity(1,1), PersonId int, CarId int)

INSERT INTO car(PersonId,CarId)
VALUES(1,2),(1,3),(1,2),(2,4)

--SELECT * FROM car

;WITH CTE as(
SELECT ROW_NUMBER() over (PARTITION BY personid,carid order by personid,carid) as rn,Id,PersonID,CarId from car)

DELETE FROM car where Id in(SELECT Id FROM CTE WHERE rn>1)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.