Xóa bản ghi trùng lặp trong SQL Server?


93

Hãy xem xét một cột có tên là EmployeeNamebảng Employee. Mục đích là để xóa các bản ghi lặp lại, dựa trên EmployeeNametrường.

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

Sử dụng một truy vấn, tôi muốn xóa các bản ghi được lặp lại.

Làm thế nào điều này có thể được thực hiện với TSQL trong SQL Server?


Ý bạn là xóa các bản ghi trùng lặp, phải không?
Sarfraz

bạn có thể chọn các giá trị riêng biệt và các ID liên quan của chúng và xóa những bản ghi có ID không có trong danh sách đã chọn?
DaeMoohn

1
bạn có một cột ID duy nhất không?
Andrew Bullock,

1
Làm thế nào bạn chấp nhận câu trả lời do John Gibb đưa ra, nếu bảng thiếu id duy nhất? đâu là empIdcột trong ví dụ của bạn được sử dụng bởi John?
armen

2
Nếu bạn không có cột ID duy nhất hoặc bất kỳ thứ gì khác có ý nghĩa để thực hiện đơn đặt hàng, bạn cũng CÓ THỂ đặt hàng theo cột tên nhân viên ... vì vậy rn của bạn sẽ là row_number() over (partition by EmployeeName order by EmployeeName)... điều này sẽ chọn một bản ghi đơn tùy ý cho mỗi tên .
John Gibb

Câu trả lời:


225

Bạn có thể làm điều này với các chức năng cửa sổ. Nó sẽ sắp xếp các bản lừa đảo bằng empId và xóa tất cả trừ cái đầu tiên.

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

Chạy nó như một lựa chọn để xem những gì sẽ bị xóa:

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

2
Nếu bạn không có khóa chính, bạn có thể sử dụng ORDER BY (SELECT NULL) stackoverflow.com/a/4812038
Arithmomaniac

35

Giả sử rằng bảng Nhân viên của bạn cũng có một cột duy nhất ( IDtrong ví dụ bên dưới), thì cách sau sẽ hoạt động:

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

Điều này sẽ để lại phiên bản có ID thấp nhất trong bảng.

Chỉnh sửa
nhận xét của Re McGyver - kể từ SQL 2012

MIN có thể được sử dụng với các cột số, char, varchar, uniqueidentifier hoặc datetime, nhưng không được sử dụng với các cột bit

Đối với 2008 R2 trở về trước,

MIN có thể được sử dụng với các cột số, ký tự, varchar hoặc ngày giờ, nhưng không được sử dụng với các cột bit (và nó cũng không hoạt động với GUID)

Đối với 2008R2, bạn sẽ cần chuyển GUIDsang kiểu được hỗ trợ bởi MIN, ví dụ:

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

SqlFiddle cho nhiều loại khác nhau trong Sql 2008

SqlFiddle cho nhiều loại khác nhau trong Sql 2012


Ngoài ra, trong Oracle, bạn có thể sử dụng "rowid" nếu không có cột id duy nhất khác.
Brandon Horsley

+1 Ngay cả khi không có cột ID, một cột có thể được thêm làm trường danh tính.
Kyle B.

Câu trả lời xuất sắc. Sắc nét và hiệu quả. Ngay cả khi bảng không có ID; tốt hơn nên bao gồm một cái để thực thi phương thức này.
MiBol

8

Bạn có thể thử một cái gì đó như sau:

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

(điều này giả định rằng bạn có một trường duy nhất dựa trên số nguyên)

Cá nhân tôi mặc dù tôi muốn nói rằng bạn nên cố gắng sửa chữa thực tế là các mục nhập trùng lặp đang được thêm vào cơ sở dữ liệu trước khi nó xảy ra hơn là một hoạt động sau khi sửa lỗi.


Tôi không có trường duy nhất (ID) trong Bảng của mình. Làm thế nào tôi có thể thực hiện các hoạt động sau đó.
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;

Sự kỳ diệu của biểu thức bảng thông thường.


SubPortal / a_horse_with_no_name - đây không phải là chọn từ một bảng thực tế? Ngoài ra, ROW_NUMBER phải là ROW_NUMBER () vì đó là một hàm, đúng không?
MacGyver

1

Thử

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

1

Nếu bạn đang tìm cách loại bỏ các bản sao nhưng lại có khóa ngoại trỏ đến bảng có các bản sao, bạn có thể thực hiện theo cách sau bằng cách sử dụng con trỏ chậm nhưng hiệu quả.

Nó sẽ định vị lại các khóa trùng lặp trên bảng khóa ngoại.

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

Vui lòng xem thêm cách xóa bên dưới.

Declare @Employee table (EmployeeName varchar(10))

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

Select * from @Employee

nhập mô tả hình ảnh ở đây

Đã tạo một bảng mẫu có tên @Employeevà tải nó với dữ liệu nhất định.

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

Select * from @Employee

Kết quả:

nhập mô tả hình ảnh ở đây

Tôi biết, điều này đã được hỏi sáu năm trước, chỉ đăng trong trường hợp nó là hữu ích cho bất kỳ ai.


-1

Đây là một cách hay để loại bỏ trùng lặp bản ghi trong bảng có cột nhận dạng dựa trên khóa chính mong muốn mà bạn có thể xác định trong thời gian chạy. Trước khi bắt đầu, tôi sẽ điền một tập dữ liệu mẫu để làm việc bằng cách sử dụng mã sau:

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

Tiếp theo, tôi sẽ tạo một Loại có tên là ColumnNames:

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

Cuối cùng, tôi sẽ tạo một proc được lưu trữ với 3 lưu ý sau: 1. Proc sẽ nhận tham số bắt buộc @tablename xác định tên của bảng mà bạn đang xóa trong cơ sở dữ liệu của mình. 2. Proc có một tham số tùy chọn @columns mà bạn có thể sử dụng để xác định các trường tạo nên khóa chính mong muốn mà bạn đang xóa. Nếu trường này bị bỏ trống, giả sử rằng tất cả các trường bên cạnh cột nhận dạng tạo thành khóa chính mong muốn. 3. Khi các bản ghi trùng lặp bị xóa, bản ghi có giá trị thấp nhất trong cột nhận dạng của nó sẽ được duy trì.

Đây là proc đã lưu delete_dupes của tôi:

 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

Khi điều này được tuân thủ, bạn có thể xóa tất cả các bản ghi trùng lặp của mình bằng cách chạy chương trình. Để xóa lỗi lừa đảo mà không xác định khóa chính mong muốn, hãy sử dụng lệnh gọi này:

exec delete_dupes '_original'

Để xóa bản sao dựa trên khóa chính mong muốn đã xác định, hãy sử dụng lệnh gọi này:

declare @table1 as columnnames
insert @table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , @table1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.