ChatGPT解决这个技术问题 Extra ChatGPT

如何删除重复的行?

从相当大的 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() 函数替换为 RowID 列,如果有)
只是在这里添加一个警告。在运行任何重复数据删除过程时,请务必先仔细检查要删除的内容!这是意外删除好数据非常常见的领域之一。

S
Srini

假设没有空值,您将 GROUP BY 唯一列和 SELECT 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 语法正确?在文档 here 中,我没有看到将表名放在 DELETE 之后作为选项。对不起,如果这对其他人来说很明显;我是 SQL 的新手,只是想学习。比它为什么起作用更重要的是:在其中包含表名有什么区别?
C
Callum Watkins

另一种可能的方法是

; 

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

执行计划

执行计划通常比公认答案中的执行计划更简单、更有效,因为它不需要自联接。

https://i.stack.imgur.com/ZJiWF.jpg

然而,情况并非总是如此。可能首选 GROUP BY 解决方案的一个地方是选择 hash aggregate 而不是流聚合的情况。

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

https://i.stack.imgur.com/iUlWm.jpg

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

分区列上没有有用的索引

相对较少的组,每组中的重复项相对较多

在第二种情况的极端版本中(如果每个组中都有很多重复项的组非常少),还可以考虑简单地插入行以保留到新表中,然后 TRUNCATE-ing 原始数据并将它们复制回来以最小化比较的日志记录删除非常高比例的行。


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

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' 关键字?我从许多其他解决方案中看到。
M
Martin Smith

以下查询对于删除重复行很有用。此示例中的表以 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 中运行良好。
C
Chloe
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”,逻辑是什么?
J
Jithin Shaji
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 子句。
F
Faisal

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

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,它会给出错误:错误代码:1093。您不能在FROM子句中指定目标表'Mytable'进行更新。但是这个小改动将适用于 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)
S
Shamseer K

我更喜欢 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”不存在,这是什么,我很困惑?
F
Factor Mystic

要获取重复行:

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,其次它不起作用,因为您不能从 DELETE 的同一个表中SELECT。在 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);
J
JuanJo

Quick and Dirty 删除完全重复的行(对于小表):

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

请注意,该问题实际上指定了非精确重复(由于行 ID)。
您还必须使用 set identity_insert t1 on 处理标识(键)列。
J
James Errico

我更喜欢内部联接的 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 具有分组依据的索引。
H
Himanshu
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 的外键引用,则截断将不起作用。
R
Ruben Verschueren

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

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:在处理这样的事情时,我总是使用事务,这不仅可以确保所有内容都作为一个整体执行,还可以让我在不冒任何风险的情况下进行测试。但是当然,您无论如何都应该进行备份以确保...


F
Faisal

这个查询对我来说显示了非常好的性能:

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


O
Ostati

使用 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。
J
Jeff Davis

在粘贴的链接 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 版本适用于这种情况,而无需在开始之前花费大量时间添加新列。
H
Haris N I

用这个

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

P
Penny Liu

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

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

为什么有人赞成这个?如果您有两个以上相同的 id,这将不起作用。而是写:从 tblemp 中删除,其中 id 不在(按标题从 tblemp 组中选择 min(id))
C
Craig

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

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

临时表解决方案和两个 mysql 示例。

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


SQL 基于多集。但是即使它是基于集合的,这两个元组 (1, a) & (2, a) 也是不同的。
c
codegoalie

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

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

这假设最多有 1 个重复项。
为什么不HAVING COUNT(*) > 1
J
Jacob Proffitt

行,可以。使用临时表。如果你想要一个“有效”的单一的、性能不是很好的声明,你可以使用:

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


I
Ismail Yavuz

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

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


a
a_m0d

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

s
shA.t

创建具有相同结构的新空白表 执行如下查询 INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count(*) > 1 然后执行此查询 INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count( *) = 1


y
yuvi

另一种方法:-

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

与 2008 年 8 月 20 日的现有答案有什么不同? - stackoverflow.com/a/18934/692942
E
Evgueny Sedov

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

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

G
Gidil

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


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

嗨,Teena,您在删除评论后错过了表 Alice 名称 T1,否则它将通过语法异常。
A
AnandPhadke
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)

L
Lauri Lubi

我想预览要删除的行并控制要保留的重复行。请参阅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

DELETE u1 FROM users u1 JOIN users u2 WHERE u1.id > u2.id AND u1.email=u2.email