Eliminare i record duplicati in SQL Server?


93

Considera una colonna denominata EmployeeNametabella Employee. L'obiettivo è eliminare i record ripetuti, in base al EmployeeNamecampo.

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

Utilizzando una query, voglio eliminare i record che vengono ripetuti.

Come è possibile farlo con TSQL in SQL Server?


Intendi eliminare i record duplicati, giusto?
Sarfraz

potresti selezionare i valori distinti e i loro ID correlati ed eliminare quei record i cui ID non sono nell'elenco già selezionato?
DaeMoohn

1
hai una colonna ID univoca?
Andrew Bullock

1
come hai accettato la risposta data da John Gibb, se la tabella manca di id univoco? dov'è la empIdcolonna nel tuo esempio usata da John?
armen

2
Se non hai una colonna ID univoca, o qualsiasi altra cosa significativa per eseguire un ordine, POTREBBE anche ordinare in base alla colonna Employeename ... quindi il tuo rn sarebbe row_number() over (partition by EmployeeName order by EmployeeName)... questo sceglierebbe un singolo record arbitrario per ogni nome .
John Gibb

Risposte:


225

Puoi farlo con le funzioni della finestra. Ordinerà i duplicati per empId ed eliminerà tutti tranne il primo.

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

Eseguilo come selezione per vedere cosa verrebbe eliminato:

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

2
Se non hai una chiave primaria, puoi utilizzare ORDER BY (SELECT NULL) stackoverflow.com/a/4812038
Arithmomaniac

35

Supponendo che la tabella Employee abbia anche una colonna univoca ( IDnell'esempio seguente), funzionerà quanto segue:

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

Ciò lascerà la versione con l'ID più basso nella tabella.

Modifica
il commento di Re McGyver - a partire da SQL 2012

MIN può essere utilizzato con colonne numeric, char, varchar, uniqueidentifier o datetime, ma non con colonne bit

Per il 2008 R2 e precedenti,

MIN può essere utilizzato con numerico, char, varchar o datetime colonne, ma non con colonne di bit (e non lo fa funzionare anche con GUID)

Per 2008R2 dovrai eseguire il cast GUIDdi un tipo supportato da MIN, ad es

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

SqlFiddle per vari tipi in Sql 2008

SqlFiddle per vari tipi in Sql 2012


Inoltre, in Oracle, è possibile utilizzare "rowid" se non sono presenti altre colonne ID univoche.
Brandon Horsley

+1 Anche se non ci fosse una colonna ID, una potrebbe essere aggiunta come campo identità.
Kyle B.

Ottima risposta. Nitido ed efficace. Anche se la tabella non ha un ID; è meglio includerne uno per eseguire questo metodo.
MiBol

8

Potresti provare qualcosa di simile a quanto segue:

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

(questo presuppone che tu abbia un campo univoco basato su un numero intero)

Personalmente, però, direi che faresti meglio a cercare di correggere il fatto che le voci duplicate vengono aggiunte al database prima che si verifichi piuttosto che come un'operazione successiva alla correzione.


Non ho il campo univoco (ID) nella mia tabella. Come posso eseguire l'operazione allora.
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;

La magia delle espressioni comuni nelle tabelle.


SubPortal / a_horse_with_no_name - non dovrebbe essere questa selezione da una tabella effettiva? Inoltre, ROW_NUMBER dovrebbe essere ROW_NUMBER () perché è una funzione, giusto?
MacGyver

1

Provare

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

1

Se stai cercando un modo per rimuovere i duplicati, ma hai una chiave esterna che punta alla tabella con i duplicati, potresti adottare il seguente approccio utilizzando un cursore lento ma efficace.

Riposizionerà le chiavi duplicate nella tabella delle chiavi esterne.

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

Si prega di vedere anche il modo di cancellazione di seguito.

Declare @Employee table (EmployeeName varchar(10))

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

Select * from @Employee

inserisci qui la descrizione dell'immagine

Creata una tabella di esempio denominata @Employeee caricata con i dati forniti.

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

Select * from @Employee

Risultato:

inserisci qui la descrizione dell'immagine

Lo so, questo è stato chiesto sei anni fa, postando solo nel caso in cui sia utile per chiunque.


-1

Ecco un bel modo per deduplicare i record in una tabella che ha una colonna Identity basata su una chiave primaria desiderata che puoi definire in fase di esecuzione. Prima di iniziare, compilerò un set di dati di esempio con cui lavorare utilizzando il codice seguente:

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

Successivamente creerò un tipo chiamato ColumnNames:

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

Infine creerò un proc memorizzato con i seguenti 3 avvertimenti: 1. Il proc prenderà un parametro richiesto @tablename che definisce il nome della tabella da cui stai eliminando nel tuo database. 2. Il proc ha un parametro opzionale @columns che puoi usare per definire i campi che compongono la chiave primaria desiderata contro cui stai eliminando. Se questo campo viene lasciato vuoto, si presume che tutti i campi oltre alla colonna Identity costituiscano la chiave primaria desiderata. 3. Quando i record duplicati vengono eliminati, verrà mantenuto il record con il valore più basso nella sua colonna Identity.

Ecco la mia procedura memorizzata delete_dupes:

 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

Una volta rispettato ciò, è possibile eliminare tutti i record duplicati eseguendo proc. Per eliminare i duplicati senza definire una chiave primaria desiderata, utilizzare questa chiamata:

exec delete_dupes '_original'

Per eliminare i duplicati in base a una chiave primaria desiderata definita, utilizzare questa chiamata:

declare @table1 as columnnames
insert @table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , @table1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.