如何删除重复行?

1373
我需要从一个相当大的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

我该怎么做?


15
针对读者使用PostgreSQL的快速提示(很多人经常被链接到这里):Pg不会将CTE项暴露为可更新的视图,因此您无法直接从CTE项中进行“DELETE FROM”操作。请参见https://dev59.com/s2Ml5IYBdhLWcg3wZGPo。 - Craig Ringer
@CraigRinger 对于 Sybase 来说也是一样的 - 我已经在这里汇总了其余的解决方案(对于PG和其他系统也应该适用:https://dev59.com/SHjZa4cB1Zd3GeqPcU6J (如果有的话)只需将ROWID()函数替换为RowID列即可)。 - maf-soft
14
这里需要加上一个警告。在运行去重过程时,一定要先仔细检查你要删除的内容!这是一个常见的错误,很容易误删好数据的领域之一。 - Jeff Davis
43个回答

21

我更喜欢使用子查询和HAVING 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
--)

它不会删除内部查询中显示的所有记录。我们只需要删除重复项并保留原始记录。 - Sandy
3
你只返回具有最低id的那个,基于select语句中的min(id)。 - James Errico
是的,但问题并不是在询问如何返回要删除的行,而是在询问如何删除重复的行。您能详细说明一下我如何删除查询返回的行吗? - Sandy
2
取消注释查询的第一行、第二行和最后一行。 - James Errico
7
这并不能清除所有的重复项。如果有3行是重复的,它只会选择ID最小的那一行进行删除,留下两行仍然是重复的。 - Chloe
2
然而,最终我不得不一遍又一遍地使用这个语句,以便它能够实际取得进展,而不是连接超时或计算机进入睡眠状态。我将其更改为MAX(id)以消除后面的重复项,并在内部查询中添加了LIMIT 1000000,以便它不必扫描整个表格。这比其他答案更快地显示了进展,其他答案似乎会挂起数小时。在表格被修剪到可管理的大小之后,您可以完成其他查询。提示:确保col1 / col2 / col3具有group by索引。 - 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

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!!

顺便说一下,当我处理这样的事情时,我总是使用一个事务,这不仅可以确保所有操作都作为一个整体执行,而且还允许我在不冒风险的情况下进行测试。但当然,你还是应该备份一下,以确保安全...


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。 - Justin R.

14

这个查询在我的情况下表现非常出色:

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

它从一个包含2M行的表中删除了超过50%为重复数据的1M行,仅用了30秒不到。


13

这是删除重复记录的最简单方法

 DELETE FROM tblemp WHERE id IN 
 (
  SELECT MIN(id) FROM tblemp
   GROUP BY  title HAVING COUNT(id)>1
 )

2
为什么有人会点赞这个?如果你有两个或以上相同的ID,这是行不通的。相反,请写成: delete from tblemp where id not in (select min(id) from tblemp group by title) - crellee

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
“易于理解”,“似乎有效”,但并没有提到方法的具体内容。请想象一下,如果链接失效了,那么知道这种方法“曾经”易于理解和有效将没有任何用处。请考虑在您的帖子中添加该方法的关键描述部分,否则这不是一个答案。 - Andriy M
这种方法适用于尚未定义标识的表格。通常您需要去除重复项以定义主键! - Jeff Davis
@JeffDavis - ROW_NUMBER 版本适用于该情况,无需在开始之前添加新列即可正常工作。 - Martin Smith

12

使用这个

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

11

我有一个表格,需要保留不重复的行。但我不确定速度或效率如何。

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

7
假设最多只有一个重复。 - Martin Smith
为什么不用HAVING COUNT(*) > 1 - Philipp M

11

好的,使用临时表。如果你想要一个单一的、不太高效的语句来“运行”,你可以使用:

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。因此,您最终会得到一个RowIDs列表,代表“原始”的非重复行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接