SQL查找删除重复行

来源:互联网 发布:福州seo基础培训 编辑:程序博客网 时间:2024/06/02 10:21

转:http://blog.csdn.net/afeiqiang/article/details/8586516

本文讲述如何查找数据库里重复的行。这是初学者十分普遍遇到的问题。方法也很简单。这个问题还可以有其他演变,例如,如何查找“两字段重复的行”(#mysql IRC 频道问到的问题)


如何查找重复行

第一步是定义什么样的行才是重复行。多数情况下很简单:它们某一列具有相同的值。本文采用这一定义,或许你对“重复”的定义比这复杂,你需要对sql做些修改。
本文要用到的数据样本
[sql] view plaincopy
  1. create table test(id int not null primary keyday date not null);  
  2.   
  3. insert into test(id, dayvalues(1, '2006-10-08');  
  4. insert into test(id, dayvalues(2, '2006-10-08');  
  5. insert into test(id, dayvalues(3, '2006-10-09');  
  6.   
  7. select * from test;  
  8. +----+------------+  
  9. | id | day        |  
  10. +----+------------+  
  11. |  1 | 2006-10-08 |  
  12. |  2 | 2006-10-08 |  
  13. |  3 | 2006-10-09 |  
  14. +----+------------+  


前面两行在day字段具有相同的值,因此如何我将他们当做重复行,这里有一查询语句可以查找。查询语句使用GROUP BY子句把具有相同字段值的行归为一组,然后计算组的大小。
[sql] view plaincopy
  1.  select daycount(*) from test GROUP BY day;  
  2. +------------+----------+  
  3. day        | count(*) |  
  4. +------------+----------+  
  5. | 2006-10-08 |        2 |  
  6. | 2006-10-09 |        1 |  
  7. +------------+----------+  

重复行的组大小大于1。如何希望只显示重复行,必须使用HAVING子句,比如
[sql] view plaincopy
  1. select daycount(*) from test group by day HAVING count(*) > 1;  
  2. +------------+----------+  
  3. day        | count(*) |  
  4. +------------+----------+  
  5. | 2006-10-08 |        2 |  
  6. +------------+----------+  

这是基本的技巧:根据具有相同值的字段分组,然后知显示大小大于1的组。


为什么不能使用WHERE子句?

因为WHERE子句过滤的是分组之前的行,HAVING子句过滤的是分组之后的行。


如何删除重复行

一个相关的问题是如何删除重复行。一个常见的任务是,重复行只保留一行,其他删除,然后你可以创建适当的索引,防止以后再有重复的行写入数据库。
同样,首先是弄清楚重复行的定义。你要保留的是哪一行呢?第一行,或者某个字段具有最大值的行?本文中,假设要保留的是第一行——id字段具有最小值的行,意味着你要删除其他的行。
也许最简单的方法是通过临时表。尤其对于MYSQL,有些限制是不能在一个查询语句中select的同时update一个表。在我的另一篇文章中 MySQL SELECT同时UPDATE同一张表(How to select from an update target in MySQL), 讲述了如何绕过这些限制。简单起见,这里只用到了临时表的方法。
我们的任务是:删除所有重复行,除了分组中id字段具有最小值的行。因此,需要找出大小大于1的分组,以及希望保留的行。你可以使用MIN()函数。这里的语句是创建临时表,以及查找需要用DELETE删除的行。
[sql] view plaincopy
  1. create temporary table to_delete (day date not null, min_id int not null);  
  2.   
  3. insert into to_delete(day, min_id)  
  4.    select dayMIN(id) from test group by day having count(*) > 1;  
  5.   
  6. select * from to_delete;  
  7. +------------+--------+  
  8. day        | min_id |  
  9. +------------+--------+  
  10. | 2006-10-08 |      1 |  
  11. +------------+--------+  

有了这些数据,你可以开始删除“脏数据”行了。可以有几种方法,各有优劣(详见我的文章many-to-one problems in SQL),但这里不做详细比较,只是说明在支持查询子句的关系数据库中,使用的标准方法。
[sql] view plaincopy
  1. delete from test  
  2.    where exists(  
  3.       select * from to_delete  
  4.       where to_delete.day = test.day and to_delete.min_id <> test.id  
  5.    )  

如何查找多列上的重复行

有人最近问到这样的问题:
  我的一个表上有两个字段b和c,分别关联到其他两个表的b和c字段。我想要找出在b字段或者c字段上具有重复值的行。
咋看很难明白,通过对话后我理解了:他想要对b和c分别创建unique索引。如上所述,查找在某一字段上具有重复值的行很简单,只要用group分组,然后计算组的大小。并且查找全部字段重复的行也很简单,只要把所有字段放到group子句。但如果是判断b字段重复或者c字段重复,问题困难得多。这里提问者用到的样本数据
[sql] view plaincopy
  1. create table a_b_c(  
  2.    a int not null primary key auto_increment,  
  3.    b int,  
  4.    c int  
  5. );  
  6.   
  7. insert into a_b_c(b,c) values (1, 1);  
  8. insert into a_b_c(b,c) values (1, 2);  
  9. insert into a_b_c(b,c) values (1, 3);  
  10. insert into a_b_c(b,c) values (2, 1);  
  11. insert into a_b_c(b,c) values (2, 2);  
  12. insert into a_b_c(b,c) values (2, 3);  
  13. insert into a_b_c(b,c) values (3, 1);  
  14. insert into a_b_c(b,c) values (3, 2);  
  15. insert into a_b_c(b,c) values (3, 3);  

现在,你可以轻易看到表里面有一些重复的行,但找不到两行具有相同的二元组{b, c}。这就是为什么问题会变得困难了。

错误的查询语句

如果把两列放在一起分组,你会得到不同的结果,具体看如何分组和计算大小。提问者恰恰是困在了这里。有时候查询语句找到一些重复行却漏了其他的。这是他用到了查询     
[sql] view plaincopy
  1. select b, c, count(*) from a_b_c  
  2. group by b, c  
  3. having count(distinct b > 1)  
  4.    or count(distinct c > 1);  

结果返回所有的行,因为CONT(*)总是1.为什么?因为 >1 写在COUNT()里面。这个错误很容易被忽略,事实上等效于
[sql] view plaincopy
  1. select b, c, count(*) from a_b_c  
  2. group by b, c  
  3. having count(1)  
  4.    or count(1);  

为什么?因为(b > 1)是一个布尔值,根本不是你想要的结果。你要的是
[sql] view plaincopy
  1. select b, c, count(*) from a_b_c  
  2. group by b, c  
  3. having count(distinct b) > 1  
  4.    or count(distinct c) > 1;  

返回空结果。很显然,因为没有重复的{b,c}。这人试了很多其他的OR和AND的组合,用来分组的是一个字段,计算大小的是另一个字段,像这样
[sql] view plaincopy
  1. select b, count(*) from a_b_c group by b having count(distinct c) > 1;  
  2. +------+----------+  
  3. | b    | count(*) |  
  4. +------+----------+  
  5. |    1 |        3 |  
  6. |    2 |        3 |  
  7. |    3 |        3 |  
  8. +------+----------+  

没有一个能够找出全部的重复行。而且最令人沮丧的是,对于某些情况,这种语句是有效的,如果错误地以为就是这么写法,然而对于另外的情况,很可能得到错误结果。

事实上,单纯用GROUP BY 是不可行的。为什么?因为当你对某一字段使用group by时,就会把另一字段的值分散到不同的分组里。对这些字段排序可以看到这些效果,正如分组做的那样。首先,对b字段排序,看看它是如何分组的

abc711812913102111221223133114321533

当你对b字段排序(分组),相同值的c被分到不同的组,因此不能用COUNT(DISTINCT c)来计算大小。COUNT()之类的内部函数只作用于同一个分组,对于不同分组的行就无能为力了。类似,如果排序的是c字段,相同值的b也会分到不同的组,无论如何是不能达到我们的目的的。

几种正确的方法

也许最简单的方法是分别对某个字段查找重复行,然后用UNION拼在一起,像这样:
[sql] view plaincopy
  1. select b as value, count(*) as cnt, 'b' as what_col  
  2.  from a_b_c group by b having count(*) > 1  
  3.  union  
  4.  select c as value, count(*) as cnt, 'c' as what_col  
  5.  from a_b_c group by c having count(*) > 1;  
  6. +-------+-----+----------+  
  7. | value | cnt | what_col |  
  8. +-------+-----+----------+  
  9. |     1 |   3 | b        |  
  10. |     2 |   3 | b        |  
  11. |     3 |   3 | b        |  
  12. |     1 |   3 | c        |  
  13. |     2 |   3 | c        |  
  14. |     3 |   3 | c        |  
  15. +-------+-----+----------+  

输出what_col字段为了提示重复的是哪个字段。另一个办法是使用嵌套查询:
[sql] view plaincopy
  1. select a, b, c from a_b_c  
  2.  where b in (select b from a_b_c group by b having count(*) > 1)  
  3.     or c in (select c from a_b_c group by c having count(*) > 1);  
  4. +----+------+------+  
  5. | a  | b    | c    |  
  6. +----+------+------+  
  7. |  7 |    1 |    1 |  
  8. |  8 |    1 |    2 |  
  9. |  9 |    1 |    3 |  
  10. | 10 |    2 |    1 |  
  11. | 11 |    2 |    2 |  
  12. | 12 |    2 |    3 |  
  13. | 13 |    3 |    1 |  
  14. | 14 |    3 |    2 |  
  15. | 15 |    3 |    3 |  
  16. +----+------+------+  

这种方法的效率要比使用UNION低许多,并且显示每一重复的行,而不是重复的字段值。还有一种方法,将自己跟group的嵌套查询结果联表查询。写法比较复杂,但对于复杂的数据或者对效率有较高要求的情况,是很有必要的。
[sql] view plaincopy
  1. select a, a_b_c.b, a_b_c.c  
  2. from a_b_c  
  3.    left outer join (  
  4.       select b from a_b_c group by b having count(*) > 1  
  5.    ) as b on a_b_c.b = b.b  
  6.    left outer join (  
  7.       select c from a_b_c group by c having count(*) > 1  
  8.    ) as c on a_b_c.c = c.c  
  9. where b.b is not null or c.c is not null  

以上方法可行,我敢肯定还有其他的方法。如果UNION能用,我想会是最简单不过的了。

原文地址 http://www.xaprb.com/blog/2006/10/09/how-to-find-duplicate-rows-with-sql/

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 平安证券账号忘了怎么办 发现发票是假的怎么办 公司收到假发票入账了怎么办 手表皮带有汗味怎么办 利客来购物卡丢了怎么办 乐天玛特倒闭卡怎么办 lv皮带买长了怎么办 密袋鼠咬了人怎么办 lv皮带如果长了怎么办 天赐农场公众号进不去了怎么办 苹果删了订阅号怎么办 蚂蚁借呗没有自动扣款怎么办 有对方qq号名字怎么办 腾讯模拟器刺激现场注册上限怎么办 丹阳智慧人社登入密码忘了怎么办? ipad系统被锁了怎么办 电脑管理员账号删了怎么办 自己电脑删文件需要管理员怎么办 苹果电脑管理员密码忘记了怎么办 电脑提示安全设置不允许下载怎么办 微信和ipad同步怎么办 苹果6空间已满怎么办 苹果6内存虚满怎么办 监控主机密码忘了怎么办 加购物车不下单怎么办 绑定qq账号消息不见了怎么办 现在的注册微信怎么办 爱奇艺手机号码被别人绑定了怎么办 手机号码换了支付宝账号怎么办 qq换手机号了怎么办呢 公司被注销了公众号怎么办 qq号被限制查找怎么办 qq号别人查找不到怎么办 qq邮箱已被注册怎么办 微信付款没网络怎么办 天猫买的假货店铺关门了怎么办 鞋小了半码怎么办 迅雷会员种子不能加速怎么办 迅雷会员为什么不能加速怎么办 持有st创智股票怎么办 租的房子床坏了怎么办