Вопрос

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

Решение

Предполагая отсутствие нулей, вы 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)))

Другие советы

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

; 

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

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

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

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

Следующий запрос полезен для удаления повторяющихся строк.Таблица в этом примере содержит 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 
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
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 

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

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)

Я бы предпочел 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)

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

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

Я предпочитаю решение подзапроса \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
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);      
SELECT  DISTINCT *
      INTO tempdb.dbo.tmpTable
FROM myTable

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

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

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:при работе над подобными вещами я всегда использую транзакцию, это не только гарантирует, что все выполняется в целом, но и позволяет мне тестировать, ничем не рискуя.Но, конечно, вам все равно следует сделать резервную копию, просто чтобы быть уверенным...

Используя 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;

Еще одно простое решение можно найти по вставленной ссылке здесь.Этот метод прост для понимания и, по-видимому, эффективен для большинства подобных проблем.Хотя это и для 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) 

Этот запрос показал очень хорошую производительность для меня:

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

он удалил 1 млн строк чуть более чем за 30 секунд из таблицы в 2 млн (50% дубликатов).

Вот еще одна хорошая статья о удаление дубликатов.

В нем обсуждается, почему это сложно:"SQL основан на реляционной алгебре, и дубликаты не могут встречаться в реляционной алгебре, поскольку дубликаты не допускаются в наборе."

Решение для временных таблиц и два примера mysql.

Собираетесь ли вы в будущем предотвращать это на уровне базы данных или с точки зрения приложения?Я бы предложил уровень базы данных, потому что ваша база данных должна отвечать за поддержание ссылочной целостности, разработчики просто создадут проблемы ;)

О, конечно.Используйте временную таблицу.Если вам нужен один не очень производительный оператор, который "работает", вы можете использовать:

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 всех строк, которые точно похожи на рассматриваемую строку.Таким образом, в итоге вы получаете список идентификаторов строк, которые представляют "исходные" неповторяющиеся строки.

У меня была таблица, в которой мне нужно было сохранить неповторяющиеся строки.Я не уверен в скорости или эффективности.

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

Другой способ заключается в Создайте новый таблица с одинаковыми полями и с Уникальным Индексом.Тогда переместить все данные из старой таблицы в новую.Автоматически SQL SERVER игнорирует (также есть опция о том, что делать, если будет дублироваться значение:игнорировать, прерывать или sth) повторяющиеся значения.Итак, у нас есть одна и та же таблица без повторяющихся строк. Если вам не нужен Уникальный индекс, после передачи данных вы можете удалить его.

Особенно для больших столов вы можете использовать DTS (пакет SSIS для импорта / экспорта данных), чтобы быстро перенести все данные в вашу новую уникально индексированную таблицу.Для 7 миллионов строк это занимает всего несколько минут.

Используй это

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

Используя приведенный ниже запрос, мы можем удалить дублирующиеся записи на основе одного столбца или нескольких столбцов.приведенный ниже запрос удален на основе двух столбцов.имя таблицы - это: 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)
  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
    

Это самый простой способ удалить дублирующуюся запись

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

http://askme.indianyouth.info/details/how-to-dumplicate-record-from-table-in-using-sql-105

Я бы упомянул этот подход, поскольку он может быть полезным и работает на всех серверах SQL:Довольно часто существует только один-два дубликата, а идентификаторы и количество дубликатов известны.В данном случае:

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

С прикладного уровня (к сожалению).Я согласен, что правильный способ предотвратить дублирование - на уровне базы данных с помощью уникального индекса, но в 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)?

DELETE
FROM
    table_name T1
WHERE
    rowid > (
        SELECT
            min(rowid)
        FROM
            table_name T2
        WHERE
            T1.column_name = T2.column_name
    );
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)

I вы хотите просмотреть строки, которые собираетесь удалить, и сохранить контроль над тем, какие из повторяющихся строк сохранить.Видишь 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 
FROM MyTable
WHERE NOT EXISTS (
              SELECT min(RowID)
              FROM Mytable
              WHERE (SELECT RowID 
                     FROM Mytable
                     GROUP BY Col1, Col2, Col3
                     ))
               );
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top