Comment puis-je supprimer les lignes en double ?
-
09-06-2019 - |
Question
Quelle est la meilleure façon de supprimer les lignes en double d'un fichier assez volumineux SQL Server
tableau (c'est-à-dire300 000+ lignes) ?
Bien entendu, les lignes ne seront pas des doublons parfaits en raison de l'existence du RowID
champ d’identité.
Ma table
RowID int not null identity(1,1) primary key,
Col1 varchar(20) not null,
Col2 varchar(2048) not null,
Col3 tinyint not null
La solution
En supposant qu'il n'y ait pas de valeurs nulles, vous GROUP BY
les colonnes uniques, et SELECT
le MIN (or MAX)
RowId comme ligne à conserver.Ensuite, supprimez simplement tout ce qui n’a pas d’identifiant de ligne :
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
Si vous avez un GUID au lieu d'un entier, vous pouvez remplacer
MIN(RowId)
avec
CONVERT(uniqueidentifier, MIN(CONVERT(char(36), MyGuidColumn)))
Autres conseils
Une autre façon possible de procéder est
;
--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;
j'utilise ORDER BY (SELECT 0)
ci-dessus car la ligne à conserver en cas d'égalité est arbitraire.
Pour conserver le dernier dans RowID
commande par exemple, vous pouvez utiliser ORDER BY RowID DESC
Plans d'exécution
Le plan d'exécution pour cela est souvent plus simple et plus efficace que celui de la réponse acceptée car il ne nécessite pas d'auto-jointure.
Ce n’est cependant pas toujours le cas.Un endroit où le GROUP BY
Une solution pourrait être préférée dans les situations où un agrégat de hachage serait choisi de préférence à un agrégat de flux.
Le ROW_NUMBER
solution donnera toujours à peu près le même plan alors que le GROUP BY
la stratégie est plus flexible.
Les facteurs qui pourraient favoriser l'approche globale de hachage seraient
- Aucun index utile sur les colonnes de partitionnement
- relativement moins de groupes avec relativement plus de doublons dans chaque groupe
Dans les versions extrêmes de ce deuxième cas (s'il y a très peu de groupes avec de nombreux doublons dans chacun), on pourrait aussi envisager simplement d'insérer les lignes à conserver dans une nouvelle table puis TRUNCATE
-ing l'original et les recopier pour minimiser la journalisation par rapport à la suppression d'une très forte proportion de lignes.
Il y a un bon article sur supprimer les doublons sur le site de support Microsoft.C'est assez conservateur - ils vous demandent de tout faire en étapes séparées - mais cela devrait bien fonctionner contre de grandes tables.
J'ai utilisé des auto-jointures pour faire cela dans le passé, même si cela pourrait probablement être agrémenté d'une clause HAVING :
DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField
AND dupes.secondDupField = fullTable.secondDupField
AND dupes.uniqueField > fullTable.uniqueField
La requête suivante est utile pour supprimer les lignes en double.Le tableau de cet exemple a ID
comme colonne d'identité et les colonnes qui ont des données en double sont Column1
, Column2
et 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)
Le script suivant montre l'utilisation de GROUP BY
, HAVING
, ORDER BY
dans une requête et renvoie les résultats avec la colonne en double et son nombre.
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
Cela supprimera les lignes en double, à l'exception de la première ligne
DELETE
FROM
Mytable
WHERE
RowID NOT IN (
SELECT
MIN(RowID)
FROM
Mytable
GROUP BY
Col1,
Col2,
Col3
)
Référer (http://www.codeproject.com/Articles/157977/Remove-Duplicate-Rows-from-a-Table-in-SQL-Server)
Je préférerais CTE pour supprimer les lignes en double de la table du serveur SQL
je recommande fortement de suivre cet article ::http://codaffection.com/sql-server-article/delete-duplicate-rows-in-sql-server/
en gardant l'original
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
sans garder l'original
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 pour supprimer exactement les lignes dupliquées (pour les petites tables) :
select distinct * into t2 from t1;
delete from t1;
insert into t1 select * from t2;
drop table t2;
Je préfère la solution subquery\having count(*) > 1 à la jointure interne car je l'ai trouvée plus facile à lire et il était très facile de la transformer en une instruction SELECT pour vérifier ce qui serait supprimé avant de l'exécuter.
--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
--)
Pour récupérer les lignes en double :
SELECT
name, email, COUNT(*)
FROM
users
GROUP BY
name, email
HAVING COUNT(*) > 1
Pour supprimer les lignes en double :
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
J'ai pensé partager ma solution car elle fonctionne dans des circonstances particulières.Dans mon cas, la table avec des valeurs en double n'avait pas de clé étrangère (car les valeurs étaient dupliquées à partir d'une autre base de données).
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 :lorsque je travaille sur des choses comme celle-ci, j'utilise toujours une transaction, cela garantit non seulement que tout est exécuté dans son ensemble, mais me permet également de tester sans rien risquer.Mais bien sûr, vous devriez quand même faire une sauvegarde juste pour être sûr...
Utilisation du CTE.L'idée est de joindre une ou plusieurs colonnes qui forment un enregistrement en double, puis de supprimer celle de votre choix :
;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;
Une autre solution simple peut être trouvée sur le lien collé ici.Celui-ci est facile à comprendre et semble efficace pour la plupart des problèmes similaires.C'est pour SQL Server mais le concept utilisé est plus qu'acceptable.
Voici les parties pertinentes de la page liée :
Considérez ces données :
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
Alors, comment pouvons-nous supprimer ces données en double ?
Tout d’abord, insérez une colonne d’identité dans cette table en utilisant le code suivant :
ALTER TABLE dbo.ATTENDANCE ADD AUTOID INT IDENTITY(1,1)
Utilisez le code suivant pour le résoudre :
DELETE FROM dbo.ATTENDANCE WHERE AUTOID NOT IN (SELECT MIN(AUTOID) _
FROM dbo.ATTENDANCE GROUP BY EMPLOYEE_ID,ATTENDANCE_DATE)
Cette requête a montré de très bonnes performances pour moi :
DELETE tbl
FROM
MyTable tbl
WHERE
EXISTS (
SELECT
*
FROM
MyTable tbl2
WHERE
tbl2.SameValue = tbl.SameValue
AND tbl.IdUniqueValue < tbl2.IdUniqueValue
)
il a supprimé 1 million de lignes en un peu plus de 30 secondes sur une table de 2 millions (50 % de doublons)
Voici un autre bon article sur supprimer les doublons.
Il explique pourquoi c'est difficile :"SQL est basé sur l'algèbre relationnelle et les doublons ne peuvent pas se produire dans l'algèbre relationnelle, car les doublons ne sont pas autorisés dans un ensemble."
La solution de table temporaire et deux exemples MySQL.
À l’avenir, allez-vous l’empêcher au niveau de la base de données ou du point de vue de l’application.Je suggérerais le niveau de la base de données car votre base de données devrait être responsable du maintien de l'intégrité référentielle, les développeurs ne feront que causer des problèmes ;)
Oh, bien sûr.Utilisez une table temporaire.Si vous voulez une instruction unique, peu performante, qui « fonctionne », vous pouvez utiliser :
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)
Fondamentalement, pour chaque ligne du tableau, la sous-sélection recherche le RowID supérieur de toutes les lignes qui ressemblent exactement à la ligne considérée.Vous vous retrouvez donc avec une liste de RowID qui représentent les lignes « originales » non dupliquées.
J'avais une table dans laquelle je devais conserver les lignes non dupliquées.Je ne suis pas sûr de la vitesse ou de l'efficacité.
DELETE FROM myTable WHERE RowID IN (
SELECT MIN(RowID) AS IDNo FROM myTable
GROUP BY Col1, Col2, Col3
HAVING COUNT(*) = 2 )
L'autre façon est Créer un nouveau table avec les mêmes champs et avec index unique.Alors déplacer toutes les données de l'ancienne table vers la nouvelle table.Ignorer automatiquement SQL SERVER (il existe également une option sur ce qu'il faut faire s'il y a une valeur en double :ignorer, interrompre ou qch) valeurs en double.Nous avons donc le même tableau sans lignes en double. Si vous ne voulez pas d'index unique, après le transfert des données, vous pouvez le supprimer.
En particulier pour les grandes tables vous pouvez utiliser DTS (package SSIS pour importer/exporter des données) afin de transférer rapidement toutes les données vers votre nouvelle table indexée de manière unique.Pour 7 millions de lignes, cela ne prend que quelques minutes.
Utilisez ceci
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
En utilisant la requête ci-dessous, nous pouvons supprimer les enregistrements en double en fonction d'une seule ou de plusieurs colonnes.la requête ci-dessous est supprimée sur la base de deux colonnes.le nom de la table est : testing
et les noms de colonnes 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)
Créer un nouveau tableau vierge avec la même structure
Exécuter une requête comme celle-ci
INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count(*) > 1
Exécutez ensuite cette requête
INSERT INTO tc_category1 SELECT * FROM tc_category GROUP BY category_id, application_id HAVING count(*) = 1
C'est le moyen le plus simple de supprimer un enregistrement en double
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
Je mentionnerais cette approche car elle peut être utile et fonctionne sur tous les serveurs SQL :Très souvent, il n’y a qu’un ou deux doublons, et les identifiants et le nombre de doublons sont connus.Dans ce cas:
SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0
Du niveau application (malheureusement).Je conviens que la bonne façon d'éviter la duplication est au niveau de la base de données grâce à l'utilisation d'un index unique, mais dans SQL Server 2005, un index ne peut contenir que 900 octets, et mon champ varchar (2048) supprime cela.
Je ne sais pas dans quelle mesure cela fonctionnerait, mais je pense que vous pourriez écrire un déclencheur pour appliquer cela, même si vous ne pouviez pas le faire directement avec un index.Quelque chose comme:
-- 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
De plus, varchar(2048) me semble louche (certaines choses dans la vie font 2048 octets, mais c'est assez rare) ;ça ne devrait vraiment pas être 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)
Si vous souhaitez prévisualiser les lignes que vous êtes sur le point de supprimer et garder le contrôle sur les lignes en double à conserver.Voir 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
))
);