随笔-33  评论-201  文章-10  trackbacks-16
 

删除表中重复记录

LazyBee

最近由于要给旧系统的表中增加主键(SQL Server2000的表),由于旧表中存在重复记录所以导致增加不上,所以需要写一段SQL语句来删除所有的重复记录(就是必须保留重复记录中的一条,维持数据记录的唯一性),我知道园子里大虾多,所以在这里集思广益,看看大家都有什么好的办法:

方法一:
     1
为了保证完整性,首先启动一个事务

2 声明一个表变量(在这里使用表变量主要是考虑重复的数据不是很多,同时为了获得更好的性能;当然如果重复的数据特别多,使用临时表是更

好的选择,因为表变量的数据都是存在内存中的,如果数据量大,可能导致内存吃紧。)用于存储重复性的数据,这个需要定义成和源表一样。

       3 将重复记录的一条插入到表变量中。

       4 删除所有有重复记录的记录

       5 将表变量中的记录插入的源表中。

       6 如果出错,回滚事务,否则提交事务

以下是相应的sql语句块:
 1Begin Tran LazyBee
 2declare @tmp Table
 3(lLIstHeader_id int,lEncounter_id int,dtLastUpdate_dt datetime,
 4 sLastUpdate_id char(10),iConcurrency_id int)
 5
 6Insert @tmp(lLIstHeader_id,lEncounter_id,dtLastUpdate_dt,sLastUpdate_id,iConcurrency_id)
 7select lListHeader_id,lEncounter_id,dtLastUpdate_dt,sLastUpdate_id,iConcurrency_id  
 8from lstHeaderencounter
 9 group by lListHeader_id,lEncounter_id,dtLastUpdate_dt,sLastUpdate_id,iConcurrency_id 
10having count(*)>1 
11
12delete lstHeaderencounter from @tmp d 
13where d.lListHeader_id=lstHeaderencounter.lListHeader_id and 
14      d.lEncounter_id=lstHeaderencounter.lEncounter_id
15
16insert lstHeaderencounter(lLIstHeader_id,lEncounter_id,dtLastUpdate_dt,sLastUpdate_id,iConcurrency_id)
17select lListHeader_id,lEncounter_id,dtLastUpdate_dt,sLastUpdate_id,iConcurrency_id 
18 from @tmp
19
20if @@error<>0
21Begin
22     print 'roll back'
23     RollBack Tran LazyBee
24  End
25else
26Begin
27   print 'Success'
28     Commit Tran LazyBee
29  End
30

我知道这个方法不够通用,因为如果有多个类似重复记录的表存在,将每次都要修改表定义和插入语句的字段内容,不知各位有没有好的方法或意见,大家讨论讨论:) 
 

刚在网上找到另外一些解决方案,感觉也挺不错的

方法二:

       1 创建一个临时表,这个临时表的结构和源表一样

       2 给这个临时表增加一个唯一索引(根据需要),并且选中忽略重复值

       3 将源表的记录全部插入临时表中,这时会因为重复记录出现3604的错误。

       4 删除源表的记录,将临时表的记录插入源表中,然后删除临时表。

方法三(主要针对重复记录完全相同的情况):

       1 利用distinct将唯一记录插入临时表中

                2 然后将唯一记录再倒回源表中
 
Select distinct * into #Tmp from tableName
Drop table tableName
Select * into tableName from #Tmp
Drop table #Tmp
  

方法四(主要针对记录部分字段相同的记录):

       这种方法和方法一有点类似,不过实现方法不同而已。在这里使用了两个临时表.我们假设重复字段为lListHeader_id,lEncounter_id,要求得到这两

个字段的唯一结果集,我们保留重复记录的第一条,当然如果保留重复记录的最后一条可以使用max代替min
      
Select identity(int,1,1as autoID, * into #Tmp from tableName
Select min(autoID) as autoID into #Tmp2 from #Tmp group by lListHeader_id,lEncounter_id
Select * from #Tmp where autoID in (Select autoID from #Tmp2)
 

方法五(也是针对部分字段相同的记录)

       SQL直接删除:
 1Delete lstStructureItems from  
 2        (select lStructure_id,lListHeader_id,lList_id,max(lParent_id) as lParent_id,max(lOrder_num) as lOrder_num,lDoctor_id 
 3         from lstStructureItems 
 4         group by lStructure_id,lListHeader_id,lList_id,lDoctor_id
 5         having count(*)>1) d 
 6 where
 7 lstStructureItems.lStructure_id=d.lStructure_id and
 8 lstStructureItems.lListHeader_id=d.lListHeader_id and
 9 lstStructureItems.lList_id=d.lList_id and
10 lstStructureItems.lDoctor_id=d.lDoctor_id and
11 (lstStructureItems.lParent_id<>d.lParent_id or
12 lstStructureItems.lOrder_num<>d.lOrder_num)
13
在这里我们为了给lStructure_id,lListHeader_id,lList_id,lDoctor_id四个字段增加一个唯一索引,所以需要只保留这四个字段保持唯一的值,其他的都需要删除。所以我们采用将这四个字段进行group by,然后保留lParent_idlOrder_num最大值的记录(注:这里假设表中不存在两条完全相同的记录,如果存在的话只能通过前面的方法来进行删除)。
Tag标签: SQL
posted on 2008-06-05 10:09 懒蜜蜂 阅读(4039) 评论(30)  编辑 收藏 网摘 所属分类: SQL

评论:
#1楼 2008-06-05 10:14 | guihwu[未注册用户]
直接用临时表就行了,何必定义表变量呢
  回复  引用    
#2楼 2008-06-05 10:16 | Richard ZZhang[未注册用户]
我觉得应该新生成一张表,目前有重复数据的表作为基表,把清理干净的数据放到另一张表中比较合理,楼下怎么看?
  回复  引用    
#3楼 2008-06-05 10:24 | Vencent_luo[未注册用户]
恩,这样还有个好处,就是可以保存原来的数据,方面出问题后查看
  回复  引用    
#4楼 2008-06-05 10:42 | Microshaoft      
老生常谈
  回复  引用  查看    
#5楼 2008-06-05 10:48 | 风海迷沙      
常规方案吧。
  回复  引用  查看    
#6楼[楼主] 2008-06-05 10:56 | 懒蜜蜂      
@guihwu
使用表变量主要是考虑重复的数据量比较小,并且速度快


  回复  引用  查看    
#7楼[楼主] 2008-06-05 11:00 | 懒蜜蜂      
@Richard ZZhang
这样的效率会不会有点慢啊,因为有大量记录在表中,目前我列举的这个表的记录有两百万条记录,重复的记录是91条。如果生成一个新的表需要先把不重复的记录插入到新表中,然后再把重复记录做distinct再插入新表中,这样将会导致大量的IO操作。兄台觉得呢?

  回复  引用  查看    
#8楼[楼主] 2008-06-05 11:02 | 懒蜜蜂      
@Vencent_luo
确实有这方面的好处,不过需要在性能之间做个选择:)

  回复  引用  查看    
#9楼 2008-06-05 12:45 | alisx      
我觉得第二个方法好,根据错误确定重复的行,如果SQL中这种错误判断机制效率比较高的话,这样借助这个错误,写SQL语句的复杂度就降低了。
  回复  引用  查看    
#10楼[楼主] 2008-06-05 13:02 | 懒蜜蜂      
@alisx
深有同感:)

  回复  引用  查看    
#11楼 2008-06-05 13:20 | rex xiang      
/* 找出重复项 */
SELECT
*
FROM
Table1 AS T1
WHERE Id = (
SELECT TOP 1 Id
FROM
Table1 AS T2
WHERE (T2.C2 = T1.C2
AND T2.C3 = T1.C3
)
ORDER BY Id ASC
)

/* 删除重复项, 保留第一条, 改成这样即可 */
DELETE FROM Table1
WHERE Id IN (
SELECT
Id
FROM
Table1 AS T1
WHERE Id <> (
SELECT TOP 1 Id
FROM
Table1 AS T2
WHERE (T2.C2 = T1.C2
AND T2.C3 = T1.C3
)
ORDER BY Id ASC -- 改成DESC, 保留最后一条.
)
)

  回复  引用  查看    
#12楼[楼主] 2008-06-05 14:28 | 懒蜜蜂      
@rex xiang
如果存在区分每一条记录的列ID,这样是可以的,如果不存在这样的列,这个sql就会有点问题了:)

  回复  引用  查看    
#13楼 2008-06-05 14:28 | KenFor[未注册用户]
个人觉得性能不需要考虑,毕竟这个动作不是经常进行的。2/8原则嘛。为了偶尔用一俩次的代码做大量优化工作不值得。把精力放在那些常用代码优化上。这个地方应该只需要考虑简单,清晰得到结果就好。
  回复  引用    
#14楼 2008-06-05 14:46 | looping      
--引用--------------------------------------------------
KenFor: 个人觉得性能不需要考虑,毕竟这个动作不是经常进行的。2/8原则嘛。为了偶尔用一俩次的代码做大量优化工作不值得。把精力放在那些常用代码优化上。这个地方应该只需要考虑简单,清晰得到结果就好。
--------------------------------------------------------
说的很好

  回复  引用  查看    
#15楼[楼主] 2008-06-05 16:51 | 懒蜜蜂      
@KenFor
@looping

非常同意二位的观点,但如果遇到数据量特别大的表,在选择方法时还是要区别对待,个人意见:)

  回复  引用  查看    
#16楼 2008-06-06 08:29 | ghtyan.cnblogs[未注册用户]
我平时用的方法与“方法四”差不多,只是笨了点^_^

我是手动给那个表添加一个自动编号的字段,然后跑到查询分析器写个sql删除重复记录(除自动编号列外),再到表设计中删除刚才加的自动编号列,OK

  回复  引用    
#17楼[楼主] 2008-06-06 08:49 | 懒蜜蜂      
@ghtyan.cnblogs

感谢参与,这个方法主要是清晰易懂,经过改进之后就可以变成一个通用的删除重复记录的存储过程了:)

  回复  引用  查看    
#18楼 2008-06-06 09:34 | music000      
我的做法差不多,如果没有标志列,新增自增标志列:
假设:stID 唯一

delete from stuinfo
WHERE (stID NOT IN
(
SELECT MIN(stID) -- MAX(stID)
FROM stuinfo
GROUP BY colA,colB,colC ...
)
)

  回复  引用  查看    
#19楼[楼主] 2008-06-06 09:44 | 懒蜜蜂      
@music000

感谢参与,可能基本上都不外乎这两种思路:)

  回复  引用  查看    
#20楼 2008-06-06 11:27 | hailibu      
我个人比较支持第三种方法:
Select distinct * into #Tmp from tableName
Drop table tableName
Select * into tableName from #Tmp
Drop table #Tmp
//////////////////////////

不过我觉得这样写可能会更好:
Select distinct * into #Tmp from tableName
Delete from tableName
Insert tableName select from #Tmp
Drop table #Tmp

  回复  引用  查看    
#21楼[楼主] 2008-06-06 11:32 | 懒蜜蜂      
@hailibu

如果更改成你写的sql语句,如果数据量比较大的话Delete操作将产生大量的日志,会使事务日志飞速增长,当然可以考虑使用truncate来避免这种情况:)

  回复  引用  查看    
#22楼 2008-06-06 17:17 | hailibu      
@懒蜜蜂
哦,原来楼主是考虑到日志的问题。惭愧得很啊,平常都没注意到这个问题,呵呵

  回复  引用  查看    
#23楼[楼主] 2008-06-06 17:29 | 懒蜜蜂      
@hailibu

其实也没什么,主要是一般情况下DBA才关心这个,我也是以前在磁盘容量快满时才发现的这个问题:)

  回复  引用  查看    
#24楼 2008-07-08 11:11 | jacarchen[未注册用户]
delete from t_system_size
where size_name in ( select size_name from t_system_size group by size_name having count(*) > 1 )

  回复  引用    
#25楼[楼主] 2008-07-08 12:42 | 懒蜜蜂      
@jacarchen

你这是把size_name字段有相同值的记录全部删除,没有保留其中的一条,如果没有要求保留其中一条的话,这是没有问题的:)

  回复  引用  查看    
#26楼 2008-07-08 20:47 | aloxc[未注册用户]
楼主有没有试过在实际生成环境中进行这样的操作(数据1000W,重复情况很小,1%这样子)服务器的负载不是很大
  回复  引用    
#27楼[楼主] 2008-07-09 09:01 | 懒蜜蜂      
@aloxc
针对方法5,就是在实际应用中的一个例子,这个表中没有1000w,但是有140w的记录,重复记录大概600条左右,速度也挺快的。

  回复  引用  查看    
#28楼 2008-07-29 17:13 | 轩之语泪      
写的很全面,收藏了!
  回复  引用  查看    
#29楼[楼主] 2008-07-29 17:20 | 懒蜜蜂      
@轩之语泪

谢谢夸奖!

  回复  引用  查看    
#30楼 2009-04-24 16:30 | cebio
在SQL SERVER 2005下可以采用CTE和ROW_NUMBER函数实现,限制比较少,不要求表中有标识符,可以根据任意属性标识重复数据。
WITH lstHeaderencounter_CTE AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY lListHeader_id, lEncounter_id ORDER BY lListHeader_id) AS rn
FROM lstHeaderencounter
)
DELETE FROM lstHeaderencounter_CTE WHERE rn > 1;

  回复  引用    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1214102




相关文章:

相关链接: