ลบระเบียนที่ซ้ำกันใน SQL Server หรือไม่


95

พิจารณาคอลัมน์ชื่อตารางEmployeeName Employeeเป้าหมายคือการลบบันทึกซ้ำโดยพิจารณาจากEmployeeNameฟิลด์

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

ใช้แบบสอบถามเดียวฉันต้องการลบระเบียนที่ซ้ำ

สิ่งนี้สามารถทำได้ด้วย TSQL ใน SQL Server?


คุณหมายถึงลบบันทึกที่ซ้ำกันใช่ไหม?
Sarfraz

คุณสามารถเลือกค่าที่แตกต่างกันและ ID ที่เกี่ยวข้องและลบระเบียนที่ ID ไม่อยู่ในรายการที่เลือกไว้แล้ว?
DaeMoohn

1
คุณมีคอลัมน์ ID เฉพาะหรือไม่
Andrew Bullock

1
คุณยอมรับคำตอบที่ได้รับจาก John Gibb ได้อย่างไรหากตารางไม่มีรหัสเฉพาะ ซึ่งเป็นempIdคอลัมน์ในตัวอย่างของคุณใช้โดยจอห์น?
Armen

2
หากคุณไม่มีคอลัมน์ ID ที่ไม่ซ้ำกันหรือสิ่งอื่นใดที่มีความหมายในการทำคำสั่งคุณสามารถสั่งซื้อตามคอลัมน์ชื่อพนักงานได้ ... ดังนั้น rn ของคุณจะเป็นrow_number() over (partition by EmployeeName order by EmployeeName)... สิ่งนี้จะเลือกระเบียนเดียวโดยพลการสำหรับแต่ละชื่อ .
John Gibb

คำตอบ:


229

คุณสามารถทำได้ด้วยฟังก์ชันหน้าต่าง มันจะสั่งการ dupes ด้วย 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

36

สมมติว่าตารางพนักงานของคุณมีคอลัมน์เฉพาะ ( IDในตัวอย่างด้านล่าง) สิ่งต่อไปนี้จะใช้ได้:

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

ซึ่งจะทำให้เวอร์ชันที่มี ID ต่ำสุดในตาราง

แก้ไข
ความคิดเห็นของ Re McGyver - ณSQL 2012

MIN สามารถใช้ได้กับคอลัมน์ตัวเลขถ่าน varchar ตัวระบุเฉพาะหรือวันที่และเวลา แต่ใช้กับคอลัมน์บิตไม่ได้

สำหรับ2008 R2และก่อนหน้านี้

MIN สามารถใช้ได้กับคอลัมน์ตัวเลขถ่าน 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" ได้หากไม่มีคอลัมน์ id เฉพาะอื่น ๆ
Brandon Horsley

+1 แม้ว่าจะไม่มีคอลัมน์ 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

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


-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))

ในที่สุดฉันจะสร้าง proc ที่จัดเก็บโดยมี 3 ข้อควรระวังต่อไปนี้: 1. proc จะใช้พารามิเตอร์ที่จำเป็น @tablename ซึ่งกำหนดชื่อของตารางที่คุณกำลังลบออกจากฐานข้อมูลของคุณ 2. proc มีพารามิเตอร์ที่เป็นทางเลือก @columns ซึ่งคุณสามารถใช้เพื่อกำหนดฟิลด์ที่ประกอบเป็นคีย์หลักที่ต้องการซึ่งคุณกำลังลบ หากช่องนี้เว้นว่างไว้จะถือว่าฟิลด์ทั้งหมดที่อยู่นอกเหนือจากคอลัมน์ identity ประกอบเป็นคีย์หลักที่ต้องการ 3. เมื่อลบระเบียนที่ซ้ำกันระเบียนที่มีค่าต่ำสุดในคอลัมน์ข้อมูลประจำตัวจะยังคงอยู่

นี่คือ 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.