【问题标题】:How can I remove duplicate rows?如何删除重复的行?
【发布时间】:2010-09-06 08:16:01
【问题描述】:

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

由于RowID 标识字段的存在,这些行当然不会是完全重复的。

我的表

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

【问题讨论】:

  • 阅读本文的 PostgreSQL 用户的快速提示(很多,取决于它链接到的频率):Pg 不会将 CTE 术语公开为可更新的视图,因此您不能 DELETE FROM 直接使用 CTE 术语。见stackoverflow.com/q/18439054/398670
  • @CraigRinger 对于 Sybase 也是如此 - 我在这里收集了剩余的解决方案(对 PG 和其他人也应该有效:stackoverflow.com/q/19544489/1855801(只需替换 @ RowID 列的 987654327@ 函数(如果有)
  • 只是在这里添加一个警告。在运行任何重复数据删除过程时,请务必先仔细检查要删除的内容!这是经常意外删除好数据的领域之一。

标签: sql-server tsql duplicates


【解决方案1】:

假设没有空值,您 GROUP BY 唯一列,@​​987654326@ MIN (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)))

【讨论】:

  • 这也行吗? DELETE FROM MyTable WHERE RowId NOT IN (SELECT MIN(RowId) FROM MyTable GROUP BY Col1, Col2, Col3);
  • @Andriy - 在 SQL Server 中,LEFT JOIN 的效率低于NOT EXISTS sqlinthewild.co.za/index.php/2010/03/23/… 同一站点还比较了NOT INNOT EXISTSsqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in 在 3 个中我认为 NOT EXISTS 表现最好。这三个都会生成一个带有自连接的计划,尽管这是可以避免的。
  • @Martin, @Georg:所以,我做了一个小测试。如下所述创建并填充了一个大表:sqlinthewild.co.za/index.php/2010/03/23/… 然后生成了两个 SELECT,一个使用 LEFT JOIN + WHERE IS NULL 技术,另一个使用 NOT IN 技术。然后我继续执行计划,你猜怎么着? LEFT JOIN 的查询成本为 18%,而 NOT IN 的查询成本为 82%,这对我来说是一个的惊喜。我可能做了一些我不应该做的事情,反之亦然,如果是真的,我真的很想知道。
  • @GeorgSchölly 提供了一个优雅的答案。我已经在我的一个 PHP 错误创建重复行的表上使用它。
  • 抱歉,为什么DELETE MyTable FROM MyTable 的语法正确?我没有看到将表名放在DELETE 之后作为文档here 中的选项。对不起,如果这对其他人来说很明显;我是 SQL 的新手,只是想学习。比它为什么起作用更重要的是:在其中包含表名有什么区别?
【解决方案2】:

另一种可能的方法是

; 

--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 解决方案的一个地方是优先选择hash aggregate 而不是流聚合的情况。

ROW_NUMBER 解决方案总是提供几乎相同的计划,而GROUP BY 策略更灵活。

可能有利于散列聚合方法的因素是

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

在第二种情况的极端版本​​中(如果每个组很少有很多重复项),也可以考虑简单地将行插入到新表中,然后 TRUNCATE-ing 原始数据并将它们复制回与删除很大比例的行相比,尽量减少日志记录。

【讨论】:

  • 如果我可以补充:接受的答案不适用于使用 uniqueidentifier 的表。这个更简单,可以在任何桌子上完美运行。谢谢马丁。
  • 这是一个很棒的答案!当我在意识到那里重复之前删除了旧的 PK 时,它起作用了。 +100
  • 我建议在 DBA.SE 上询问然后回答这个问题(用这个答案)。然后我们可以添加到our list of canonical answers
  • 与接受的答案不同,这也适用于没有要比较的键 (RowId) 的表。
  • 另一方面,这并不适用于所有 SQL Server 版本
【解决方案3】:

在 Microsoft 支持网站上有一篇关于 removing duplicates 的好文章。这是相当保守的 - 他们让你在不同的步骤中完成所有事情 - 但它应该适用于大型表。

我过去曾使用自联接来执行此操作,尽管它可能会被 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 上删除重复行的最有效方法。谢谢!
  • 更加简单易懂!
  • 我有一个疑问,在您的 sql 查询中,为什么在 'DELETE' 之后不使用 'From' 关键字?我从许多其他解决方案中看到。
【解决方案4】:

以下查询对于删除重复行很有用。本例中的表有ID作为标识列,有重复数据的列是Column1Column2Column3

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 

【讨论】:

  • 第一个脚本出现 MySQL 错误'您不能在 FROM 子句中指定目标表 'TableName' 进行更新'
  • 除了D.Rosado已经报错外,你的第一次查询也很慢。相应的 SELECT 查询占用了我的设置 +- 比接受的答案长 20 倍。
  • @parvus - 问题标记为 SQL Server 而不是 MySQL。 SQL Server 中的语法很好。 MySQL 在优化子查询see for example here 方面也是出了名的糟糕。这个答案在 SQL Server 中很好。事实上NOT IN 的表现通常比OUTER JOIN ... NULL 好。我会在查询中添加一个HAVING MAX(ID) IS NOT NULL,尽管在语义上它不应该是必要的,因为这可以改进计划example of that here
  • 在 PostgreSQL 8.4 中运行良好。
【解决方案5】:
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 因为 postgres 用户也来到这里。看看这个答案的分数。
  • 我在一些流行的 SQL 问题中看到了这一点,例如 hereherehere。 OP得到了他的答案,其他人也得到了一些帮助。恕我直言没问题。
  • 在一个查询中,您在删除后使用“From”,而在一个查询中您没有使用“From”,逻辑是什么?
【解决方案6】:
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 

【讨论】:

  • 我在 azure SQL DW 上收到此消息:DELETE 语句中当前不支持 FROM 子句。
【解决方案7】:

这将删除重复的行,除了第一行

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)

【讨论】:

  • 对于 mysql 会报错:Error Code: 1093. You can't specify target table 'Mytable' for update in FROM 子句。但是这个小改动将适用于 mysql: DELETE FROM Mytable WHERE RowID NOT IN ( SELECT ID FROM (SELECT MIN(RowID) AS ID FROM Mytable GROUP BY Col1,Col2,Col3) AS TEMP)
【解决方案8】:

我更喜欢 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)

【讨论】:

  • 在一个查询中,您在删除后使用“from”,而另一个“from”不存在,这是什么,我很困惑?
【解决方案9】:

获取重复行:

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 @987654325来自@ing。在 MySQL 中,这会引发MySQL error 1093
  • 我认为这比使用 DELETE FROM ... LEFT OUTER JOIN 的相当深奥的接受答案更合理,这在某些系统(例如 SQL Server)上也不起作用。如果遇到上述限制,您始终可以将选择的结果保存到临时 TABLE 变量中:DECLARE @idsToKeep TABLE(rowid INT);,然后是 INSERT INTO @idsToKeep(rowid) SELECT MIN... GROUP BY ...,然后是 DELETE users WHERE rowid NOT IN (SELECT rowid FROM @idsToKeep);
【解决方案10】:

快速删除完全重复的行(对于小表):

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

【讨论】:

  • 请注意,问题实际上指定了非精确重复(由于行 id)。
  • 您还必须使用set identity_insert t1 on 处理身份(键)列。
【解决方案11】:

我更喜欢 subquery\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
--)

【讨论】:

  • 它不会删除所有显示在内部查询中的记录。我们只需要删除重复项并保留原件。
  • 根据 select 子句中的 min(id),您只返回 id 最低的那个。
  • 取消注释掉查询的第一行、第二行和最后一行。
  • 这不会清除所有重复项。如果您有 3 行重复,它将仅选择具有 MIN(id) 的行,并删除该行,留下两行重复。
  • 尽管如此,我最终还是一遍又一遍地重复使用此语句,以便它实际上会取得进展,而不是让连接超时或计算机进入睡眠状态。我将其更改为MAX(id) 以消除后面的重复,并将LIMIT 1000000 添加到内部查询中,这样它就不必扫描整个表。这表明进展比其他答案快得多,其他答案似乎要挂几个小时。将表修剪到可管理的大小后,您可以完成其他查询。提示:确保 col1/col2/col3 具有分组依据的索引。
【解决方案12】:
SELECT  DISTINCT *
      INTO tempdb.dbo.tmpTable
FROM myTable

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

【讨论】:

  • 如果您有对 myTable 的外键引用,则截断将不起作用。
【解决方案13】:

我想我会分享我的解决方案,因为它适用于特殊情况。 我的情况是具有重复值的表没有外键(因为这些值是从另一个数据库复制的)。

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% 重复)

    【讨论】:

      【解决方案15】:

      使用 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;
      

      【讨论】:

      • 我认为您在 JOIN 中缺少 AND。
      【解决方案16】:

      在粘贴的链接here 中可以找到另一个简单的解决方案。这很容易掌握,并且似乎对大多数类似问题都有效。虽然它适用于 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) 
      

      【讨论】:

      • “容易掌握”、“好像有效”,但没有说方法包含什么。想象一下链接失效了,那么知道方法有什么用是否易于掌握且有效?请考虑将方法描述的重要部分添加到您的帖子中,否则这不是答案。
      • 此方法对于尚未定义标识的表很有用。通常你需要去掉重复项才能定义主键!
      • @JeffDavis - ROW_NUMBER 版本适用于这种情况,无需在开始之前添加新列。
      【解决方案17】:

      使用这个

      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
      

      【讨论】:

        【解决方案18】:

        这是removing duplicates上的另一篇好文章。

        它讨论了它的难点:“SQL 是基于关系代数的,在关系代数中不能出现重复,因为集合中不允许出现重复。

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

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

        【讨论】:

        • SQL 基于多集。但是即使是基于集合的,这两个元组 (1, a) & (2, a) 也是不同的。
        【解决方案19】:

        我有一张表,我需要在其中保留不重复的行。 我不确定速度或效率。

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

        【讨论】:

        • 这假设最多有 1 个重复。
        • 为什么不HAVING COUNT(*) &gt; 1
        【解决方案20】:

        哦,当然。使用临时表。如果你想要一个“有效”的单一的、性能不是很好的语句,你可以使用:

        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 列表。

        【讨论】:

          【解决方案21】:

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

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

          【讨论】:

          • 为什么有人支持这个?如果您有两个以上相同的 id,这将不起作用。而是写:从 tblemp 中删除,其中 id 不在 (select min(id) from tblemp group by title)
          【解决方案22】:

          另一种方法是创建一个具有相同字段和具有唯一索引的新表。然后将所有数据从旧表移到新表。自动 SQL SERVER 忽略(如果会有重复值,还有一个选项:忽略、中断或 sth)重复值。所以我们有同一张表,没有重复的行。 如果您不想要唯一索引,可以在传输数据后将其删除

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

          【讨论】:

            【解决方案23】:

            通过使用下面的查询,我们可以删除基于单列或多列的重复记录。下面的查询是基于两列删除的。表名是: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)
            

            【讨论】:

              【解决方案24】:
              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
                

              【讨论】:

                【解决方案25】:

                另一种方法:--

                DELETE A
                FROM   TABLE A,
                       TABLE B
                WHERE  A.COL1 = B.COL1
                       AND A.COL2 = B.COL2
                       AND A.UNIQUEFIELD > B.UNIQUEFIELD 
                

                【讨论】:

                【解决方案26】:

                我会提到这种方法,因为它很有帮助,并且适用于所有 SQL 服务器: 通常只有一两个重复项,并且 Id 和重复项的数量是已知的。在这种情况下:

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

                【讨论】:

                  【解决方案27】:

                  从应用程序级别(不幸的是)。我同意防止重复的正确方法是在数据库级别通过使用唯一索引,但在 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) 吗?

                  【讨论】:

                    【解决方案28】:
                    DELETE
                    FROM
                        table_name T1
                    WHERE
                        rowid > (
                            SELECT
                                min(rowid)
                            FROM
                                table_name T2
                            WHERE
                                T1.column_name = T2.column_name
                        );
                    

                    【讨论】:

                    • 嗨,Teena,您在删除注释后错过了表 Alice 名称 T1,否则它将通过语法异常。
                    【解决方案29】:
                    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)
                    

                    【讨论】:

                      【解决方案30】:

                      您想预览要删除的行并控制要保留的重复行。见http://developer.azurewebsites.net/2014/09/better-sql-group-by-find-duplicate-data/

                      with MYCTE as (
                        SELECT ROW_NUMBER() OVER (
                          PARTITION BY DuplicateKey1
                                      ,DuplicateKey2 -- optional
                          ORDER BY CreatedAt -- the first row among duplicates will be kept, other rows will be removed
                        ) RN
                        FROM MyTable
                      )
                      DELETE FROM MYCTE
                      WHERE RN > 1
                      

                      【讨论】:

                        猜你喜欢
                        • 2019-05-27
                        • 2021-05-22
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-11-22
                        • 2018-03-21
                        • 2011-03-21
                        • 2018-01-22
                        相关资源
                        最近更新 更多