Как я могу удалить дубликаты строк? - PullRequest
1221 голосов
/ 21 августа 2008

Каков наилучший способ удаления повторяющихся строк из довольно большой таблицы 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

Ответы [ 37 ]

1102 голосов
/ 21 августа 2008

При условии отсутствия нулей, вы GROUP BY уникальные столбцы и SELECT MIN (or MAX) RowId в качестве строки для сохранения. Затем просто удалите все, что не имеет идентификатора строки:

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)))
731 голосов
/ 29 сентября 2010

Другой возможный способ сделать это -

; 

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

Планы выполнения

План выполнения этого часто проще и эффективнее, чем в принятом ответе, поскольку не требует самостоятельного соединения.

Execution Plans

Однако это не всегда так. Единственное место, где решение GROUP BY может быть предпочтительным, - это ситуации, когда хеш-агрегат будет выбран предпочтительнее агрегата потока.

Решение ROW_NUMBER всегда дает один и тот же план, тогда как стратегия GROUP BY более гибкая.

Execution Plans

Факторы, которые могут благоприятствовать подходу агрегирования хэшей, будут

  • Нет полезного индекса для столбцов разделения
  • относительно меньше групп с относительно большим количеством дубликатов в каждой группе

В крайних версиях этого второго случая (если существует очень мало групп с большим количеством дубликатов в каждой), можно также подумать о том, чтобы просто вставить строки для сохранения в новую таблицу, а затем TRUNCATE с оригиналом и скопировать его свести к минимуму ведение журнала по сравнению с удалением очень высокой пропорции строк.

141 голосов
/ 21 августа 2008

На сайте поддержки Microsoft есть хорошая статья о удалении дубликатов . Это довольно консервативно - они заставляют вас делать все в отдельных шагах - но это должно хорошо работать с большими таблицами.

В прошлом я использовал для этого самостоятельные объединения, хотя, вероятно, это можно было бы добавить к предложению HAVING:

DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField 
AND dupes.secondDupField = fullTable.secondDupField 
AND dupes.uniqueField > fullTable.uniqueField
91 голосов
/ 23 ноября 2011

Следующий запрос полезен для удаления дублирующихся строк. Таблица в этом примере имеет ID в качестве столбца идентификаторов, а столбцы с дублирующимися данными: Column1, Column2 и 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 BY, HAVING, ORDER BY в одном запросе и возвращает результаты с повторяющимся столбцом и его количеством.

SELECT YourColumnName,
       COUNT(*) TotalCount
FROM   YourTableName
GROUP  BY YourColumnName
HAVING COUNT(*) > 1
ORDER  BY COUNT(*) DESC 
58 голосов
/ 30 сентября 2010
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
42 голосов
/ 21 мая 2014
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 
38 голосов
/ 10 сентября 2013

Это удалит повторяющиеся строки, кроме первой строки

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)

29 голосов
/ 19 мая 2015

Я бы предпочел 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)
22 голосов
/ 06 февраля 2013

Быстрое и грязное удаление точных дублированных строк (для небольших таблиц):

select  distinct * into t2 from t1;
delete from t1;
insert into t1 select *  from t2;
drop table t2;
20 голосов
/ 01 марта 2014

Я предпочитаю решение подзапроса \ имеющее 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
--)
...