SQL Server에서 중복 레코드를 삭제 하시겠습니까?


93

EmployeeNametable 이라는 열을 고려하십시오 Employee. 목표는 EmployeeName필드를 기반으로 반복되는 레코드를 삭제하는 것 입니다.

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

하나의 쿼리를 사용하여 반복되는 레코드를 삭제하고 싶습니다.

SQL Server에서 TSQL로 어떻게이 작업을 수행 할 수 있습니까?


중복 기록을 삭제하는 거죠?
Sarfraz

고유 한 값과 관련 ID를 선택하고 ID가 이미 선택된 목록에없는 레코드를 삭제할 수 있습니까?
DaeMoohn

1
고유 한 ID 열이 있습니까?
Andrew Bullock

1
테이블에 고유 ID가없는 경우 John Gibb의 답변을 어떻게 받아들였습니까? empIdJohn이 사용한 예제 의 열 은 어디에 있습니까?
armen 2013 년

2
고유 한 ID 열이 없거나 주문을 수행하는 데 의미있는 다른 항목이없는 경우 employeename 열을 기준으로 주문할 수도 있습니다. 따라서 rn은 row_number() over (partition by EmployeeName order by EmployeeName)... 각 이름에 대해 임의의 단일 레코드를 선택합니다. .
John Gibb 2013

답변:


225

창 기능으로이를 수행 할 수 있습니다. 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

Employee 테이블에도 고유 한 열이 있다고 가정하면 ( ID아래 예에서) 다음이 작동합니다.

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

그러면 테이블에서 ID가 가장 낮은 버전이 남습니다.


Re McGyver의 주석 편집 -SQL 2012 기준

MIN numeric, 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 
);

Sql 2008의 다양한 유형에 대한 SqlFiddle

Sql 2012의 다양한 유형에 대한 SqlFiddle


또한 Oracle에서는 다른 고유 ID 열이없는 경우 "rowid"를 사용할 수 있습니다.
Brandon Horsley

+1 ID 열이 없더라도 ID 필드로 추가 할 수 있습니다.
Kyle B.

훌륭한 대답입니다. 선명하고 효과적입니다. 테이블에 ID가 없더라도; 이 메서드를 실행하려면 하나를 포함하는 것이 좋습니다.
MiBol

8

다음과 같은 것을 시도 할 수 있습니다.

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

(이것은 정수 기반 고유 필드가 있다고 가정합니다)

개인적으로는 수정 후 작업보다는 중복 항목이 데이터베이스에 추가되기 전에 데이터베이스에 추가된다는 사실을 수정하는 것이 더 낫다고 말하고 싶습니다.


내 테이블에 고유 한 필드 (ID)가 없습니다. 그러면 작업을 어떻게 수행 할 수 있습니까?
usr021986 2010

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 2014


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

결과:

여기에 이미지 설명 입력

6 년 전에 질문 한 것입니다. 누군가에게 도움이 될 수 있도록 게시했습니다.


-1

다음은 런타임에 정의 할 수있는 원하는 기본 키를 기반으로 ID 열이있는 테이블에서 레코드를 중복 제거하는 좋은 방법입니다. 시작하기 전에 다음 코드를 사용하여 작업 할 샘플 데이터 세트를 채울 것입니다.

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 가지주의 사항과 함께 저장된 proc을 생성합니다. 1. proc은 데이터베이스에서 삭제하려는 테이블의 이름을 정의하는 필수 매개 변수 @tablename을 사용합니다. 2. proc에는 삭제하려는 기본 키를 구성하는 필드를 정의하는 데 사용할 수있는 선택적 매개 변수 @columns가 있습니다. 이 필드를 비워두면 ID 열을 제외한 모든 필드가 원하는 기본 키를 구성한다고 가정합니다. 3. 중복 레코드가 삭제되면 ID 열에서 가장 낮은 값을 가진 레코드가 유지됩니다.

다음은 내 delete_dupes 저장된 proc입니다.

 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.