Видалити дублікати записів у SQL Server?


94

Розглянемо стовпець з назвою EmployeeNameтаблиця Employee. Мета - видалити повторювані записи на основі EmployeeNameполя.

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

За допомогою одного запиту я хочу видалити записи, які повторюються.

Як це можна зробити за допомогою TSQL в SQL Server?


Ви маєте на увазі видалити дублікати записів, так?
Сарфраз

Ви можете вибрати різні значення та пов'язані з ними ідентифікатори та видалити ті записи, ідентифікатори яких відсутні у вже вибраному списку?
DaeMoohn

1
у вас є унікальний стовпець ідентифікатора?
Ендрю Баллок,

1
як ви прийняли відповідь, дану Джоном Гіббом, якщо в таблиці відсутні унікальні ідентифікатори? де empIdстовпець у вашому прикладі використовується Джоном?
армен

2
Якщо у вас немає унікального стовпця ідентифікатора або чогось іншого, що має значення для виконання замовлення, ви також можете замовити за стовпцем імені співробітника ... так що ваш rn буде row_number() over (partition by EmployeeName order by EmployeeName)... це буде вибрати довільний одиничний запис для кожного імені .
Джон Гібб,

Відповіді:


227

Це можна зробити за допомогою віконних функцій. Він впорядкує обману за допомогою empId і видалить усі, крім першого.

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

Запустіть його як вибір, щоб побачити, що буде видалено:

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

2
Якщо у вас немає первинного ключа, ви можете використовувати ORDER BY (SELECT NULL) stackoverflow.com/a/4812038
Arithmomaniac

35

Якщо припустити, що у вашій таблиці службовців також є унікальний стовпець ( IDу прикладі нижче), буде працювати наступне:

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

Це залишить у таблиці версію з найнижчим ідентифікатором.

Редагувати
коментар Ре Макгівера - станом на SQL 2012

MIN може використовуватися із числовими, char, varchar, uniqueidentifier або datetime стовпцями, але не з бітовими стовпцями

Для 2008 R2 і раніше,

MIN може використовуватися з числовими, char, varchar або datetime стовпцями, але не з бітовими стовпцями (і це також не працює з GUID)

Для 2008R2 вам потрібно буде GUIDпередати тип до типу, який підтримується MIN, наприклад

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

SqlFiddle для різних типів у Sql 2008

SqlFiddle для різних типів у Sql 2012


Крім того, в Oracle ви можете використовувати "rowid", якщо немає іншого унікального стовпця ідентифікатора.
Брендон Хорслі,

+1 Навіть якби не було стовпця ідентифікатора, його можна було б додати як поле ідентичності.
Kyle B.

Відмінна відповідь. Різкий та ефективний. Навіть якщо таблиця не має ідентифікатора; для виконання цього методу краще включити такий.
MiBol

8

Ви можете спробувати щось на зразок наступного:

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

(це передбачає наявність у вас цілочисельного унікального поля)

Особисто я хотів би сказати, що вам краще було б спробувати виправити той факт, що дублікати записів додаються до бази даних до того, як це відбувається, а не як операція виправлення після виправлення.


У мене немає унікального поля (ID) у моїй таблиці. Як я можу тоді виконати операцію?
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;

Магія загальних табличних виразів.


SubPortal / a_horse_with_no_name - чи не слід це вибирати з фактичної таблиці? Крім того, ROW_NUMBER має бути ROW_NUMBER (), оскільки це функція, чи не так?
MacGyver


1

Якщо ви шукаєте спосіб видалення дублікатів, але у вас є зовнішній ключ, який вказує на таблицю з дублікатами, ви можете застосувати наступний підхід, використовуючи повільний, але ефективний курсор.

Він перемістить дублікати ключів до таблиці зовнішнього ключа.

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

Будь ласка, ознайомтесь із наведеним нижче способом видалення.

Declare @Employee table (EmployeeName varchar(10))

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

Select * from @Employee

введіть тут опис зображення

Створив зразок таблиці з іменем @Employeeта завантажив її із заданими даними.

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

Select * from @Employee

Результат:

введіть тут опис зображення

Я знаю, це запитували шість років тому, розміщуючи повідомлення просто на випадок, якщо це корисно для когось.


-1

Ось приємний спосіб дедуплікації записів у таблиці, яка має стовпець ідентичності на основі бажаного первинного ключа, який ви можете визначити під час виконання. Перш ніж почати, я заповнюю зразок даних, набір для роботи, використовуючи такий код:

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

Далі я буду створювати тип, який називається ColumnNames:

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

Нарешті, я створив збережену процедуру із наступними 3 застереженнями: 1. Процес прийме необхідний параметр @tablename, який визначає ім'я таблиці, яку ви видаляєте у вашій базі даних. 2. Процес має необов’язковий параметр @columns, за допомогою якого можна визначити поля, що складають бажаний первинний ключ, проти якого ви видаляєте. Якщо це поле залишити пустим, передбачається, що всі поля, крім стовпця ідентифікації, складають бажаний первинний ключ. 3. Коли дублікати записів видаляються, запис із найменшим значенням у його стовпці ідентифікації буде збережений.

Ось мій файл 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

Після того, як це виконано, ви можете видалити всі дублікати записів, запустивши proc. Для видалення дурнів без визначення бажаного первинного ключа використовуйте цей виклик:

exec delete_dupes '_original'

Щоб видалити дурні на основі визначеного бажаного первинного ключа, використовуйте цей виклик:

declare @table1 as columnnames
insert @table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , @table1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.