Come posso rimuovere le righe duplicate?
-
09-06-2019 - |
Domanda
Qual è il modo migliore per rimuovere le righe duplicate da un file abbastanza grande SQL Server
tabella (es.300.000+ righe)?
Le righe, ovviamente, non saranno duplicati perfetti a causa dell'esistenza del file RowID
campo identitario.
MyTable
RowID int not null identity(1,1) primary key,
Col1 varchar(20) not null,
Col2 varchar(2048) not null,
Col3 tinyint not null
Soluzione
Supponendo che non ci siano valori nulli, tu GROUP BY
le colonne univoche e SELECT
IL MIN (or MAX)
RowId come riga da mantenere.Quindi, elimina semplicemente tutto ciò che non aveva un ID riga:
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
Nel caso in cui tu abbia un GUID invece di un numero intero, puoi sostituirlo
MIN(RowId)
con
CONVERT(uniqueidentifier, MIN(CONVERT(char(36), MyGuidColumn)))
Altri suggerimenti
Un altro modo possibile per farlo è
;
--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;
sto usando ORDER BY (SELECT 0)
sopra in quanto è arbitrario quale riga conservare in caso di parità.
Per preservare l'ultimo arrivato RowID
ordina ad esempio che potresti usare ORDER BY RowID DESC
Piani di esecuzione
Il piano di esecuzione per questo è spesso più semplice ed efficiente di quello nella risposta accettata poiché non richiede l'auto-unione.
Tuttavia non è sempre così.Un posto dove il GROUP BY
la soluzione potrebbe essere preferita nelle situazioni in cui a aggregato di hash verrebbe scelto preferibilmente rispetto a un aggregato di flussi.
IL ROW_NUMBER
la soluzione fornirà sempre più o meno lo stesso piano mentre il GROUP BY
la strategia è più flessibile.
I fattori che potrebbero favorire l’approccio aggregato dell’hash sarebbero
- Nessun indice utile nelle colonne di partizionamento
- relativamente meno gruppi con relativamente più duplicati in ciascun gruppo
Nelle versioni estreme di questo secondo caso (se ci sono pochissimi gruppi con molti duplicati in ciascuno) si potrebbe anche considerare semplicemente di inserire le righe da conservare in una nuova tabella quindi TRUNCATE
-recuperare gli originali e copiarli nuovamente per ridurre al minimo la registrazione rispetto all'eliminazione di una percentuale molto elevata di righe.
C'è un bell'articolo su rimozione dei duplicati sul sito del supporto Microsoft.È piuttosto conservativo: ti fanno fare tutto in passaggi separati, ma dovrebbe funzionare bene su tavoli di grandi dimensioni.
Ho utilizzato i self-join per farlo in passato, anche se probabilmente potrebbe essere abbellito con una clausola HAVING:
DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField
AND dupes.secondDupField = fullTable.secondDupField
AND dupes.uniqueField > fullTable.uniqueField
La seguente query è utile per eliminare righe duplicate.La tabella in questo esempio ha ID
come colonna identità e le colonne che contengono dati duplicati lo sono Column1
, Column2
E 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)
Lo script seguente mostra l'utilizzo di GROUP BY
, HAVING
, ORDER BY
in una query e restituisce i risultati con la colonna duplicata e il relativo conteggio.
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
Postgre:
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
Ciò eliminerà le righe duplicate, tranne la prima riga
DELETE
FROM
Mytable
WHERE
RowID NOT IN (
SELECT
MIN(RowID)
FROM
Mytable
GROUP BY
Col1,
Col2,
Col3
)
Fare riferimento (http://www.codeproject.com/Articles/157977/Remove-Duplicate-Rows-from-a-Table-in-SQL-Server)
Preferirei CTE per eliminare le righe duplicate dalla tabella del server SQL
consiglio vivamente di seguire questo articolo:http://codaffection.com/sql-server-article/delete-duplicate-rows-in-sql-server/
mantenendo l'originale
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
senza mantenere l'originale
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)
Quick and Dirty per eliminare le righe duplicate esatte (per tabelle di piccole dimensioni):
select distinct * into t2 from t1;
delete from t1;
insert into t1 select * from t2;
drop table t2;
Preferisco la soluzione subquery\have count(*) > 1 all'inner join perché l'ho trovata più facile da leggere ed è stato molto facile trasformarla in un'istruzione SELECT per verificare cosa sarebbe stato eliminato prima di eseguirlo.
--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
--)
Per recuperare righe duplicate:
SELECT
name, email, COUNT(*)
FROM
users
GROUP BY
name, email
HAVING COUNT(*) > 1
Per eliminare le righe duplicate:
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
Ho pensato di condividere la mia soluzione poiché funziona in circostanze speciali.Nel mio caso la tabella con valori duplicati non aveva una chiave esterna (perché i valori erano duplicati da un altro db).
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:quando lavoro su cose del genere utilizzo sempre una transazione, questo non solo garantisce che tutto venga eseguito nel suo insieme, ma mi permette anche di testare senza rischiare nulla.Ma ovviamente dovresti comunque fare un backup per essere sicuro...
Utilizzando CTE.L'idea è di unire una o più colonne che formano un record duplicato e quindi rimuovere quella che preferisci:
;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;
Ancora un'altra soluzione semplice può essere trovata nel collegamento incollato Qui.Questo è facile da comprendere e sembra essere efficace per la maggior parte dei problemi simili.È per SQL Server, ma il concetto utilizzato è più che accettabile.
Ecco le parti rilevanti dalla pagina collegata:
Considera questi dati:
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
Allora come possiamo eliminare quei dati duplicati?
Innanzitutto, inserisci una colonna Identity in quella tabella utilizzando il seguente codice:
ALTER TABLE dbo.ATTENDANCE ADD AUTOID INT IDENTITY(1,1)
Utilizzare il seguente codice per risolverlo:
DELETE FROM dbo.ATTENDANCE WHERE AUTOID NOT IN (SELECT MIN(AUTOID) _
FROM dbo.ATTENDANCE GROUP BY EMPLOYEE_ID,ATTENDANCE_DATE)
Questa query ha mostrato ottime prestazioni per me:
DELETE tbl
FROM
MyTable tbl
WHERE
EXISTS (
SELECT
*
FROM
MyTable tbl2
WHERE
tbl2.SameValue = tbl.SameValue
AND tbl.IdUniqueValue < tbl2.IdUniqueValue
)
ha eliminato 1 milione di righe in poco più di 30 secondi da una tabella di 2 milioni (50% di duplicati)
Ecco un altro buon articolo su rimozione dei duplicati.
Spiega perché è difficile:"SQL è basato sull'algebra relazionale e i duplicati non possono verificarsi nell'algebra relazionale, perché i duplicati non sono consentiti in un set."
La soluzione della tabella temporanea e due esempi MySQL.
In futuro lo impedirai a livello di database o dal punto di vista dell'applicazione.Suggerirei il livello del database perché il tuo database dovrebbe essere responsabile del mantenimento dell'integrità referenziale, gli sviluppatori causerebbero solo problemi;)
Oh certo.Utilizza una tabella temporanea.Se vuoi una singola affermazione non molto performante che "funzioni" puoi scegliere:
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)
Fondamentalmente, per ogni riga della tabella, la sottoselezione trova il RowID superiore di tutte le righe che sono esattamente come la riga in questione.Quindi ti ritroverai con un elenco di RowID che rappresentano le righe "originali" non duplicate.
Avevo una tabella in cui dovevo preservare le righe non duplicate.Non sono sicuro della velocità o dell'efficienza.
DELETE FROM myTable WHERE RowID IN (
SELECT MIN(RowID) AS IDNo FROM myTable
GROUP BY Col1, Col2, Col3
HAVING COUNT(*) = 2 )
L'altro modo è Creane uno nuovo tabella con gli stessi campi e con indice univoco.Poi spostare tutti i dati dalla vecchia tabella alla nuova tabella.SQL SERVER ignora automaticamente (c'è anche un'opzione su cosa fare se sarà presente un valore duplicato:ignorare, interrompere o qc) valori duplicati.Quindi abbiamo la stessa tabella senza righe duplicate. Se non desideri l'Indice Univoco, dopo il trasferimento dei dati puoi eliminarlo.
Particolarmente per tavoli più grandi puoi utilizzare DTS (pacchetto SSIS per importare/esportare dati) per trasferire rapidamente tutti i dati nella tua nuova tabella indicizzata in modo univoco.Per 7 milioni di righe bastano pochi minuti.
Usa questo
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
Utilizzando la query seguente possiamo eliminare record duplicati in base alla colonna singola o a più colonne.la query seguente viene eliminata in base a due colonne.il nome della tabella è: testing
e nomi di colonne 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)
Crea una nuova tabella vuota con la stessa struttura
Esegui query in questo modo
INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count(*) > 1
Quindi esegui questa query
INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count(*) = 1
Questo è il modo più semplice per eliminare i record duplicati
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
Vorrei menzionare questo approccio perché può essere utile e funziona su tutti i server SQL:Molto spesso ci sono solo uno o due duplicati e gli ID e il conteggio dei duplicati sono noti.In questo caso:
SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0
Dal livello applicativo (purtroppo).Sono d'accordo che il modo corretto per prevenire la duplicazione è a livello di database attraverso l'uso di un indice univoco, ma in SQL Server 2005 un indice può contenere solo 900 byte e il mio campo varchar(2048) lo spazza via.
Non so quanto funzionerebbe, ma penso che potresti scrivere un trigger per applicarlo, anche se non potresti farlo direttamente con un indice.Qualcosa di simile a:
-- 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
Inoltre, varchar(2048) mi sembra sospetto (alcune cose nella vita sono 2048 byte, ma è piuttosto raro);non dovrebbe davvero essere 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)
Vuoi visualizzare in anteprima le righe che stai per rimuovere e mantenere il controllo su quali righe duplicate conservare.Vedere 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
))
);