Excluir registros duplicados no SQL Server?

93

Considere uma coluna chamada EmployeeNametabela Employee. O objetivo é excluir registros repetidos, com base no EmployeeNamecampo.

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

Usando uma consulta, desejo excluir os registros que se repetem.

Como isso pode ser feito com TSQL no SQL Server?

usr021986
fonte
Quer dizer deletar registros duplicados, certo?
Sarfraz
você poderia selecionar os valores distintos e seus IDs relacionados e excluir os registros cujos IDs não estão na lista já selecionada?
DaeMoohn
1
você tem uma coluna de ID exclusiva?
Andrew Bullock
1
como você aceitou a resposta dada por John Gibb, se a tabela não tem um id único? onde está a empIdcoluna em seu exemplo usada por John?
armen
2
Se você não tiver uma coluna de ID exclusiva, ou qualquer outra coisa significativa para fazer um pedido, você PODERIA também ordenar pela coluna nome do funcionário ... então seu rn seria row_number() over (partition by EmployeeName order by EmployeeName)... isso escolheria um único registro arbitrário para cada nome .
John Gibb

Respostas:

225

Você pode fazer isso com funções de janela. Ele ordenará os duplicados por empId e excluirá todos, exceto o primeiro.

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

Execute-o como um select para ver o que seria excluído:

select *
from (
  select *, rn=row_number() over (partition by EmployeeName order by empId)
  from Employee 
) x
where rn > 1;
John Gibb
fonte
2
Se você não tiver uma chave primária, pode usar ORDER BY (SELECT NULL) stackoverflow.com/a/4812038
Arithmomaniac
35

Supondo que sua tabela Employee também tenha uma coluna exclusiva ( IDno exemplo abaixo), o seguinte funcionará:

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

Isso deixará a versão com o ID mais baixo na tabela.

Editar
o comentário de Re McGyver - a partir do SQL 2012

MIN pode ser usado com colunas numéricas, char, varchar, uniqueidentifier ou datetime, mas não com colunas de bits

Para 2008 R2 e anterior,

MIN pode ser usado com colunas numéricas, char, varchar ou datetime, mas não com colunas de bits (e também não funciona com GUIDs)

Para 2008R2, você precisará converter o GUIDpara um tipo compatível MIN, por exemplo,

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

SqlFiddle para vários tipos no Sql 2008

SqlFiddle para vários tipos no Sql 2012

StuartLC
fonte
Além disso, no Oracle, você pode usar "rowid" se não houver outra coluna de id exclusiva.
Brandon Horsley
+1 Mesmo se não houvesse uma coluna de ID, uma poderia ser adicionada como um campo de identidade.
Kyle B.
Excelente resposta. Nítido e eficaz. Mesmo que a tabela não tenha um ID; é melhor incluir um para executar este método.
MiBol
8

Você pode tentar algo como o seguinte:

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

(isso pressupõe que você tem um campo exclusivo baseado em número inteiro)

Pessoalmente, porém, diria que seria melhor você tentar corrigir o fato de que entradas duplicadas estão sendo adicionadas ao banco de dados antes que ocorra, em vez de como uma operação pós-correção.

Ben Cawley
fonte
Não tenho o campo exclusivo (ID) em minha tabela. Como posso realizar a operação então.
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
Kumar Manish
fonte
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;

A magia das expressões de mesa comuns.

Mostafa Elmoghazi
fonte
SubPortal / a_horse_with_no_name - não deveria ser uma seleção de uma tabela real? Além disso, ROW_NUMBER deve ser ROW_NUMBER () porque é uma função, correto?
MacGyver de
1

Experimentar

DELETE
FROM employee
WHERE rowid NOT IN (SELECT MAX(rowid) FROM employee
GROUP BY EmployeeName);
Anurag Garg
fonte
1

Se você está procurando uma maneira de remover duplicatas, mas tem uma chave estrangeira apontando para a tabela com duplicatas, você pode seguir a seguinte abordagem usando um cursor lento, mas eficaz.

Ele irá realocar as chaves duplicadas na tabela de chaves estrangeiras.

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
Peter
fonte
0
delete from person 
where ID not in
(
        select t.id from 
        (select min(ID) as id from person 
         group by email 
        ) as t
);
ohsoifelse
fonte
-1

Por favor, veja a forma de exclusão abaixo também.

Declare @Employee table (EmployeeName varchar(10))

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

Select * from @Employee

insira a descrição da imagem aqui

Criou uma tabela de amostra chamada @Employeee carregou-a com os dados fornecidos.

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

Select * from @Employee

Resultado:

insira a descrição da imagem aqui

Eu sei, isso é perguntado há seis anos, postar apenas no caso de ser útil para qualquer pessoa.

Jithin Shaji
fonte
-1

Esta é uma boa maneira de desduplicar registros em uma tabela que possui uma coluna de identidade baseada em uma chave primária desejada que você pode definir em tempo de execução. Antes de começar, vou preencher um conjunto de dados de amostra para trabalhar com o seguinte código:

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

Em seguida, criarei um tipo chamado ColumnNames:

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

Finalmente, criarei um proc armazenado com as três seguintes advertências: 1. O proc terá um parâmetro obrigatório @tablename que define o nome da tabela que você está excluindo do banco de dados. 2. O proc tem um parâmetro opcional @columns que você pode usar para definir os campos que constituem a chave primária desejada que você está excluindo. Se este campo for deixado em branco, presume-se que todos os campos além da coluna de identidade constituem a chave primária desejada. 3. Quando registros duplicados são excluídos, o registro com o menor valor em sua coluna de identidade será mantido.

Aqui está meu procedimento armazenado 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

Uma vez que isso seja cumprido, você pode deletar todos os seus registros duplicados executando o proc. Para excluir ingênuos sem definir uma chave primária desejada, use esta chamada:

exec delete_dupes '_original'

Para excluir ingênuos com base em uma chave primária desejada definida, use esta chamada:

declare @table1 as columnnames
insert @table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , @table1
Daniel Marcus
fonte