Каков наилучший способ удалить повторяющиеся строки из довольно большой 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
Предполагая , что не аннулирует, то GROUP BY
уникальные колонны, и RowId как ряд , чтобы сохранить. Затем просто удалите все, что не имеет идентификатора строки:SELECT
MIN (or MAX)
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);
LEFT JOIN
является менее эффективным , чем NOT EXISTS
sqlinthewild.co.za/index.php/2010/03/23/... Тот же сайт также сравнивает NOT IN
против NOT EXISTS
. sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in Из трех я считаю, что NOT EXISTS
работает лучше всего. Все три создадут план с самостоятельным объединением, хотя этого можно избежать. DELETE MyTable FROM MyTable
правильный синтаксис? Я не вижу размещения имени таблицы сразу после DELETE
опции в документации здесь . Извините, если это очевидно для других; Я новичок в SQL, просто пытаюсь учиться. Что важнее, чем почему это работает: в чем разница между включением имени таблицы или нет? На сайте поддержки Microsoft есть хорошая статья об удалении дубликатов . Это довольно консервативно - они заставляют вас делать все в отдельных шагах - но это должно хорошо работать с большими столами.
Я использовал для этого самостоятельные объединения в прошлом, хотя, вероятно, это можно было бы с помощью предложения HAVING:
DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField
AND dupes.secondDupField = fullTable.secondDupField
AND dupes.uniqueField > fullTable.uniqueField
Вот еще одна хорошая статья по удалению дубликатов .
Здесь обсуждается, почему это сложно: « 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 всех строк, которые в точности соответствуют рассматриваемой строке. Таким образом, вы получите список RowID, которые представляют «оригинальные» недублированные строки.
С уровня приложения (к сожалению). Я согласен, что правильный способ предотвращения дублирования на уровне базы данных - использование уникального индекса, но в 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)?
Создать новую пустую таблицу с той же структурой
Выполнить запрос, как это
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
У меня была таблица, где мне нужно было сохранить неповторяющиеся строки. Я не уверен в скорости или эффективности.
DELETE FROM myTable WHERE RowID IN (
SELECT MIN(RowID) AS IDNo FROM myTable
GROUP BY Col1, Col2, Col3
HAVING COUNT(*) = 2 )
HAVING COUNT(*) > 1
? Другой возможный способ сделать это
;
--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
решение может быть предпочтительным, - это ситуации, когда хеш-агрегат будет выбран предпочтительнее, чем агрегат потока.
ROW_NUMBER
Решение всегда будет давать в значительной степени тот же план , тогда как GROUP BY
стратегия является более гибкой.
Факторы, которые могли бы способствовать подходу агрегирования хэшей
- Нет полезного индекса для столбцов разделения
- относительно меньше групп с относительно большим количеством дубликатов в каждой группе
В крайних версиях этого второго случая (если существует очень мало групп с множеством дубликатов в каждой), можно также рассмотреть возможность просто вставить строки, чтобы сохранить их в новую таблицу, затем TRUNCATE
-в оригинале и скопировать их обратно, чтобы минимизировать ведение журнала по сравнению с удалением очень высокая пропорция рядов.
uniqueidentifier
. Этот намного проще и отлично работает на любом столе. Спасибо Мартин. RowId
) для сравнения. 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
Следующий запрос полезен для удаления повторяющихся строк. Таблицы в этом примере , имеют в 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
NOT IN
часто работает лучше, чем OUTER JOIN ... NULL
. Я бы добавил HAVING MAX(ID) IS NOT NULL
к запросу, хотя семантически это не нужно, поскольку это может улучшить примерИспользуя приведенный ниже запрос, мы можем удалить дубликаты записей на основе одного или нескольких столбцов. ниже запрос удаляется на основе двух столбцов. Имя таблицы: 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)
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)
SELECT DISTINCT *
INTO tempdb.dbo.tmpTable
FROM myTable
TRUNCATE TABLE myTable
INSERT INTO myTable SELECT * FROM tempdb.dbo.tmpTable
DROP TABLE tempdb.dbo.tmpTable
Я хотел бы упомянуть этот подход, а также то, что он может быть полезен и работает на всех серверах SQL: довольно часто есть только один - два дубликата, и идентификаторы и количество дубликатов известны. В таком случае:
SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0
Быстро и грязно удалить точно дублированные строки (для небольших таблиц):
select distinct * into t2 from t1;
delete from t1;
insert into t1 select * from t2;
drop table t2;
Еще одно простое решение можно найти по ссылке, вставленной здесь . Это легко понять и кажется эффективным для большинства подобных проблем. Это для 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)
ROW_NUMBER
Версия отлично подходит для этого случая, не нужно вдаваться в подробности добавления нового столбца перед началом. Это удалит повторяющиеся строки, кроме первой строки
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 )
Другой способ - создать новую таблицу с теми же полями и уникальным индексом . Затем переместите все данные из старой таблицы в новую таблицу . Автоматически SQL SERVER игнорирует (есть также вариант, что делать, если будет повторяющееся значение: игнорировать, прерывать или sth) повторяющихся значений. Итак, у нас одна и та же таблица без повторяющихся строк. Если вы не хотите уникальный индекс, после передачи данных вы можете удалить его .
Специально для больших таблиц вы можете использовать DTS (пакет SSIS для импорта / экспорта данных), чтобы быстро перенести все данные в вашу новую уникально проиндексированную таблицу. Для 7 миллионов подряд это займет всего несколько минут.
DELETE
FROM
table_name T1
WHERE
rowid > (
SELECT
min(rowid)
FROM
table_name T2
WHERE
T1.column_name = T2.column_name
);
DELETE
FROM MyTable
WHERE NOT EXISTS (
SELECT min(RowID)
FROM Mytable
WHERE (SELECT RowID
FROM Mytable
GROUP BY Col1, Col2, Col3
))
);
Я думал, что поделюсь своим решением, так как оно работает при особых обстоятельствах. В моем случае таблица с дублирующимися значениями не имела внешнего ключа (потому что значения были продублированы из другой базы данных).
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: при работе над такими вещами я всегда использую транзакцию, это не только гарантирует, что все выполняется целиком, но и позволяет мне тестировать, не рискуя ничем. Но, конечно, вы должны сделать резервную копию в любом случае, просто чтобы быть уверенным ...
Я предпочитаю решение подзапроса \ имеющее 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
--)
MAX(id)
устранить последние дубликаты, и добавил LIMIT 1000000
к внутреннему запросу, чтобы не пришлось сканировать всю таблицу. Это показало прогресс намного быстрее, чем другие ответы, которые, казалось бы, зависали часами. После того, как таблица была сокращена до управляемого размера, вы можете закончить с другими запросами. Совет: убедитесь, что col1 / col2 / col3 имеют индексы для группировки по. DELETE LU
FROM (SELECT *,
Row_number()
OVER (
partition BY col1, col1, col3
ORDER BY rowid DESC) [Row]
FROM mytable) LU
WHERE [row] > 1
Используя 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;
Этот запрос показал очень хорошую производительность для меня:
DELETE tbl
FROM
MyTable tbl
WHERE
EXISTS (
SELECT
*
FROM
MyTable tbl2
WHERE
tbl2.SameValue = tbl.SameValue
AND tbl.IdUniqueValue < tbl2.IdUniqueValue
)
он удалил 1M строк за чуть более 30 секунд из таблицы 2M (50% дубликатов)
Если вы хотите предварительно просмотреть строки, которые вы собираетесь удалить, и сохранить контроль над тем, какие из дубликатов строки оставить. Смотрите 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
Я бы предпочел CTE для удаления дублирующихся строк из таблицы сервера SQL
Настоятельно рекомендуем следовать этой статье :: 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)
Использовать это
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
alter table MyTable add sno int identity(1,1)
delete from MyTable where sno in
(
select sno from (
select *,
RANK() OVER ( PARTITION BY RowID,Col3 ORDER BY sno DESC )rank
From MyTable
)T
where rank>1
)
alter table MyTable
drop column sno
Теперь давайте посмотрим таблицу elasticalsearch, в которой в этих таблицах есть дублирующиеся строки, а Id - это идентичное поле uniq. Мы знаем, если какой-то идентификатор существует по групповым критериям, тогда мы можем удалить другие строки вне этой группы. Моя манера показывает этот критерий.
Так много случаев этой темы находятся в моем подобном состоянии. Просто измените критерии целевой группы в соответствии с вашим случаем удаления повторяющихся (дублированных) строк.
DELETE
FROM elasticalsearch
WHERE Id NOT IN
(SELECT min(Id)
FROM elasticalsearch
GROUP BY FirmId,FilterSearchString
)
ура
DELETE FROM
напрямую использовать термин CTE. См. Stackoverflow.com/q/18439054/398670ROWID()
функцию на столбец RowID, если есть)