Doppelte Datensätze in SQL Server löschen?


93

Betrachten Sie eine Spalte mit dem Namen EmployeeNametable Employee. Ziel ist es, wiederholte Datensätze basierend auf dem EmployeeNameFeld zu löschen .

EmployeeName
------------
Anand
Anand
Anil
Dipak
Anil
Dipak
Dipak
Anil

Mit einer Abfrage möchte ich die Datensätze löschen, die wiederholt werden.

Wie geht das mit TSQL in SQL Server?


Du meinst doppelte Datensätze löschen, oder?
Sarfraz

Sie könnten die unterschiedlichen Werte und die zugehörigen IDs auswählen und die Datensätze löschen, deren IDs nicht in der bereits ausgewählten Liste enthalten sind.
DaeMoohn

1
Haben Sie eine eindeutige ID-Spalte?
Andrew Bullock

1
Wie haben Sie die Antwort von John Gibb akzeptiert, wenn der Tabelle eine eindeutige ID fehlt? Wo wird die empIdSpalte in Ihrem Beispiel von John verwendet?
Armen

2
Wenn Sie keine eindeutige ID-Spalte oder etwas anderes haben, das für eine Bestellung von Bedeutung ist, KÖNNEN Sie auch nach der Spalte für den Mitarbeiternamen bestellen ... also wäre Ihr RN row_number() over (partition by EmployeeName order by EmployeeName)... dies würde einen beliebigen einzelnen Datensatz für jeden Namen auswählen .
John Gibb

Antworten:


225

Sie können dies mit Fensterfunktionen tun. Die Dupes werden nach empId sortiert und alle bis auf den ersten gelöscht.

delete x from (
  select *, rn=row_number() over (partition by EmployeeName order by empId)
  from Employee 
) x
where rn > 1;

Führen Sie es als Auswahl aus, um zu sehen, was gelöscht werden soll:

select *
from (
  select *, rn=row_number() over (partition by EmployeeName order by empId)
  from Employee 
) x
where rn > 1;

2
Wenn Sie keinen Primärschlüssel haben, können Sie ORDER BY (SELECT NULL) stackoverflow.com/a/4812038
Arithmomaniac

35

Angenommen, Ihre Mitarbeitertabelle verfügt auch über eine eindeutige Spalte ( IDim folgenden Beispiel), funktioniert Folgendes:

delete from Employee 
where ID not in
(
    select min(ID)
    from Employee 
    group by EmployeeName 
);

Dadurch bleibt die Version mit der niedrigsten ID in der Tabelle.

Bearbeiten
Sie den Kommentar von Re McGyver - ab SQL 2012

MIN kann mit numerischen, char-, varchar-, uniqueidentifier- oder datetime-Spalten verwendet werden, jedoch nicht mit Bitspalten

Für 2008 R2 und früher

MIN kann mit numerischen, char-, varchar- oder datetime-Spalten verwendet werden, jedoch nicht mit Bit-Spalten (und funktioniert auch nicht mit GUIDs).

Für 2008R2 müssen Sie das GUIDin einen Typ umwandeln MIN, der z

delete from GuidEmployees
where CAST(ID AS binary(16)) not in
(
    select min(CAST(ID AS binary(16)))
    from GuidEmployees
    group by EmployeeName 
);

SqlFiddle für verschiedene Typen in Sql 2008

SqlFiddle für verschiedene Typen in Sql 2012


In Oracle können Sie auch "rowid" verwenden, wenn keine andere eindeutige ID-Spalte vorhanden ist.
Brandon Horsley

+1 Auch wenn keine ID-Spalte vorhanden wäre, könnte eine als Identitätsfeld hinzugefügt werden.
Kyle B.

Hervorragende Antwort. Scharf und effektiv. Auch wenn die Tabelle keine ID hat; Es ist besser, eine einzuschließen, um diese Methode auszuführen.
MiBol

8

Sie könnten Folgendes ausprobieren:

delete T1
from MyTable T1, MyTable T2
where T1.dupField = T2.dupField
and T1.uniqueField > T2.uniqueField  

(Dies setzt voraus, dass Sie ein ganzzahliges eindeutiges Feld haben.)

Persönlich würde ich jedoch sagen, dass Sie besser dran sind, die Tatsache zu korrigieren, dass doppelte Einträge zur Datenbank hinzugefügt werden, bevor sie auftreten, als als Post-Fix-It-Operation.


Ich habe kein eindeutiges Feld (ID) in meiner Tabelle. Wie kann ich dann die Operation durchführen?
usr021986

3
DELETE
FROM MyTable
WHERE ID NOT IN (
     SELECT MAX(ID)
     FROM MyTable
     GROUP BY DuplicateColumn1, DuplicateColumn2, DuplicateColumn3)

WITH TempUsers (FirstName, LastName, duplicateRecordCount)
AS
(
    SELECT FirstName, LastName,
    ROW_NUMBER() OVER (PARTITIONBY FirstName, LastName ORDERBY FirstName) AS duplicateRecordCount
    FROM dbo.Users
)
DELETE
FROM TempUsers
WHERE duplicateRecordCount > 1

3
WITH CTE AS
(
   SELECT EmployeeName, 
          ROW_NUMBER() OVER(PARTITION BY EmployeeName ORDER BY EmployeeName) AS R
   FROM employee_table
)
DELETE CTE WHERE R > 1;

Die Magie gängiger Tabellenausdrücke.


SubPortal / a_horse_with_no_name - sollte dies nicht aus einer tatsächlichen Tabelle ausgewählt werden? Außerdem sollte ROW_NUMBER ROW_NUMBER () sein, da es sich um eine Funktion handelt, richtig?
MacGyver

1

Versuchen

DELETE
FROM employee
WHERE rowid NOT IN (SELECT MAX(rowid) FROM employee
GROUP BY EmployeeName);

1

Wenn Sie nach einer Möglichkeit suchen, Duplikate zu entfernen, aber ein Fremdschlüssel auf die Tabelle mit Duplikaten zeigt, können Sie den folgenden Ansatz mit einem langsamen, aber effektiven Cursor wählen.

Die doppelten Schlüssel in der Fremdschlüsseltabelle werden verschoben.

create table #properOlvChangeCodes(
    id int not null,
    name nvarchar(max) not null
)

DECLARE @name VARCHAR(MAX);
DECLARE @id INT;
DECLARE @newid INT;
DECLARE @oldid INT;

DECLARE OLVTRCCursor CURSOR FOR SELECT id, name FROM Sales_OrderLineVersionChangeReasonCode; 
OPEN OLVTRCCursor;
FETCH NEXT FROM OLVTRCCursor INTO @id, @name;
WHILE @@FETCH_STATUS = 0  
BEGIN  
        -- determine if it should be replaced (is already in temptable with name)
        if(exists(select * from #properOlvChangeCodes where Name=@name)) begin
            -- if it is, finds its id
            Select  top 1 @newid = id
            from    Sales_OrderLineVersionChangeReasonCode
            where   Name = @name

            -- replace terminationreasoncodeid in olv for the new terminationreasoncodeid
            update Sales_OrderLineVersion set ChangeReasonCodeId = @newid where ChangeReasonCodeId = @id

            -- delete the record from the terminationreasoncode
            delete from Sales_OrderLineVersionChangeReasonCode where Id = @id
        end else begin
            -- insert into temp table if new
            insert into #properOlvChangeCodes(Id, name)
            values(@id, @name)
        end

        FETCH NEXT FROM OLVTRCCursor INTO @id, @name;
END;
CLOSE OLVTRCCursor;
DEALLOCATE OLVTRCCursor;

drop table #properOlvChangeCodes

0
delete from person 
where ID not in
(
        select t.id from 
        (select min(ID) as id from person 
         group by email 
        ) as t
);

-1

Bitte beachten Sie auch die unten stehende Art der Löschung.

Declare @Employee table (EmployeeName varchar(10))

Insert into @Employee values 
('Anand'),('Anand'),('Anil'),('Dipak'),
('Anil'),('Dipak'),('Dipak'),('Anil')

Select * from @Employee

Geben Sie hier die Bildbeschreibung ein

Erstellt eine Beispieltabelle mit dem Namen @Employeeund lädt sie mit den angegebenen Daten.

Delete  aliasName from (
Select  *,
        ROW_NUMBER() over (Partition by EmployeeName order by EmployeeName) as rowNumber
From    @Employee) aliasName 
Where   rowNumber > 1

Select * from @Employee

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Ich weiß, das wird vor sechs Jahren gefragt, nur für den Fall, dass es für irgendjemanden hilfreich ist.


-1

Hier ist eine gute Möglichkeit, Datensätze in einer Tabelle zu deduplizieren, deren Identitätsspalte auf einem gewünschten Primärschlüssel basiert, den Sie zur Laufzeit definieren können. Bevor ich anfange, fülle ich einen Beispieldatensatz mit dem folgenden Code auf:

if exists (select 1 from sys.all_objects where type='u' and name='_original')
drop table _original

declare @startyear int = 2017
declare @endyear int = 2018
declare @iterator int = 1
declare @income money = cast((SELECT round(RAND()*(5000-4990)+4990 , 2)) as money)
declare @salesrepid int = cast(floor(rand()*(9100-9000)+9000) as varchar(4))
create table #original (rowid int identity, monthyear varchar(max), salesrepid int, sale money)
while @iterator<=50000 begin
insert #original 
select (Select cast(floor(rand()*(@endyear-@startyear)+@startyear) as varchar(4))+'-'+ cast(floor(rand()*(13-1)+1) as varchar(2)) ),  @salesrepid , @income
set  @salesrepid  = cast(floor(rand()*(9100-9000)+9000) as varchar(4))
set @income = cast((SELECT round(RAND()*(5000-4990)+4990 , 2)) as money)
set @iterator=@iterator+1
end  
update #original
set monthyear=replace(monthyear, '-', '-0') where  len(monthyear)=6

select * into _original from #original

Als nächstes werde ich einen Typ namens ColumnNames erstellen:

create type ColumnNames AS table   
(Columnnames varchar(max))

Schließlich werde ich einen gespeicherten Prozess mit den folgenden 3 Einschränkungen erstellen: 1. Der Prozess verwendet einen erforderlichen Parameter @tabellenname, der den Namen der Tabelle definiert, aus der Sie in Ihrer Datenbank löschen. 2. Der Prozess verfügt über einen optionalen Parameter @columns, mit dem Sie die Felder definieren können, aus denen der gewünschte Primärschlüssel besteht, für den Sie löschen. Wenn dieses Feld leer gelassen wird, wird davon ausgegangen, dass alle Felder außer der Identitätsspalte den gewünschten Primärschlüssel bilden. 3. Wenn doppelte Datensätze gelöscht werden, wird der Datensatz mit dem niedrigsten Wert in der Identitätsspalte beibehalten.

Hier ist mein delete_dupes gespeicherter Prozess:

 create proc delete_dupes (@tablename varchar(max), @columns columnnames readonly) 
 as
 begin

declare @table table (iterator int, name varchar(max), is_identity int)
declare @tablepartition table (idx int identity, type varchar(max), value varchar(max))
declare @partitionby varchar(max)  
declare @iterator int= 1 


if exists (select 1 from @columns)  begin
declare @columns1 table (iterator int, columnnames varchar(max))
insert @columns1
select 1, columnnames from @columns
set @partitionby = (select distinct 
                substring((Select ', '+t1.columnnames 
                From @columns1 t1
                Where T1.iterator = T2.iterator
                ORDER BY T1.iterator
                For XML PATH ('')),2, 1000)  partition
From @columns1 T2 )

end

insert @table 
select 1, a.name, is_identity from sys.all_columns a join sys.all_objects b on a.object_id=b.object_id
where b.name = @tablename  

declare @identity varchar(max)= (select name from @table where is_identity=1)

while @iterator>=0 begin 
insert @tablepartition
Select          distinct case when @iterator=1 then 'order by' else 'over (partition by' end , 
                substring((Select ', '+t1.name 
                From @table t1
                Where T1.iterator = T2.iterator and is_identity=@iterator
                ORDER BY T1.iterator
                For XML PATH ('')),2, 5000)  partition
From @table T2
set @iterator=@iterator-1
end 

declare @originalpartition varchar(max)

if @partitionby is null begin
select @originalpartition  = replace(b.value+','+a.type+a.value ,'over (partition by','')  from @tablepartition a cross join @tablepartition b where a.idx=2 and b.idx=1
select @partitionby = a.type+a.value+' '+b.type+a.value+','+b.value+') rownum' from @tablepartition a cross join @tablepartition b where a.idx=2 and b.idx=1
 end
 else
 begin
 select @originalpartition=b.value +','+ @partitionby from @tablepartition a cross join @tablepartition b where a.idx=2 and b.idx=1
 set @partitionby = (select 'OVER (partition by'+ @partitionby  + ' ORDER BY'+ @partitionby + ','+b.value +') rownum'
 from @tablepartition a cross join @tablepartition b where a.idx=2 and b.idx=1)
 end


exec('select row_number() ' + @partitionby +', '+@originalpartition+' into ##temp from '+ @tablename+'')


exec(
'delete a from _original a 
left join ##temp b on a.'+@identity+'=b.'+@identity+' and rownum=1  
where b.rownum is null')

drop table ##temp

end

Sobald dies erfüllt ist, können Sie alle Ihre doppelten Datensätze löschen, indem Sie den Prozess ausführen. Verwenden Sie diesen Aufruf, um Dupes zu löschen, ohne einen gewünschten Primärschlüssel zu definieren:

exec delete_dupes '_original'

Verwenden Sie diesen Aufruf, um Dupes basierend auf einem definierten gewünschten Primärschlüssel zu löschen:

declare @table1 as columnnames
insert @table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , @table1
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.