删除重复记录保留1条

删除重复的数据,目前有两种策略,一种是,分组统计,找到唯一的数据,选择保留,或在重复的范围中,取反删除不唯一的条目。另外一种思路,联同一张表,进行查找删除。

在删除重复数据中,需要注意:1、如果是删除操作,注意该表可能还在有新增的操作,注意不能影响到新插入的数据。2、能分批操作,分批。(但是分批,一般来说也不太好操作)

文章以下的内容,是需要建立在以下的认识。1、全体样本。全体样本,可能还在变化,如新增、删除、更新。(如果是这种复杂情况,建议需要三思)2、全体样本中的唯一。这个应该是要保留的数据(group by )3、重复样本全体分组,注意是分组,我们还可以对它进行运算。(group by + having count(*)>1)这个里面,包含了全部的重复数据,但是不能直接删除它们,因为要从他们中间保存1条。4、重复样本分组max/min,(max + group by + having count()> 1)

文章:

https://www.cnblogs.com/chongcheng/p/12900841.html

https://segmentfault.com/a/1190000019458642

分组

利用分组 group by 字段1,字段2,字段2 + having count(*) > 1来实现找到重复的数据。

方式一(保留):

然后从中挑选出唯一的数据,然后选择插入到另外一长表或者删除掉,

方式二(删除):

或者删除除已找到的数据以外的所有数据。(方法较麻烦,某些情况下,查询表,又删除该表的内容,可能会触发错误,需要用到子查询。)

相当于排除法,具体排除有就种方式。

例子

单字段

在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢
1、查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断

select * from people
where peopleId in (select peopleId from people group by peopleId having count(peopleId) > 1)

2、删除表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断,只留有rowid最小的记录

delete from people
where  peopleName in (select peopleName  from people group by peopleName   having count(peopleName) > 1)
and  peopleId not in (select min(peopleId) from people group by peopleName   having count(peopleName)>1)

多字段

3、查找表中多余的重复记录(多个字段)

select * from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)

4、删除表中多余的重复记录(多个字段),只留有rowid最小的记录

delete from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1)

5、查找表中多余的重复记录(多个字段),不包含rowid最小的记录

select * from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1) 

插入到新表

不删除原有的表的数据,将整理后的不重复的数据插入到新表中。

只想包含局部的列,不想要全部的列,如,原表的id不要。如下。可能的情况有,由于没有id的限制,下面的语句可以执行多次,会导致数据重复。

INSERT INTO td_mail_03 ( ship_id, send_id, recv_id, kafka_time, insert_time )
SELECT ship_id,
		 send_id,
		 recv_id,
		 kafka_time,
		 insert_time
FROM td_mail_02
WHERE "id" IN 
    (SELECT MIN (ID)
    FROM td_mail_02
    GROUP BY  ship_id, recv_id, send_id )

全部的列都复制。两者的表结构一致。则可以使用更简单的sql。如下:

INSERT INTO td_mail_03  
SELECT *
FROM td_mail_02
WHERE "id" IN 
    (SELECT MIN (ID)
    FROM td_mail_02
    GROUP BY  ship_id, recv_id, send_id )

删除法

在删除的时候,要注意,此时系统是否还在新入库数据。如果还在新入库数据,排除的时候,最好加上范围,如:between(min,max),进行分批处理,这样,避免删除掉新添加的数据。在大量数据的时候,非常有必要。

方法1:

该方法排除的比较麻烦。另外,按下列方式进行排除,需要重复执行两个子查询,子查询本身是耗费时间的,如果在此旗舰,又增加了数据,下面删除,可能不妥。

delete from user_info 
where (name,sex,age) in ( 
	select x.* from ( -- 删除的时候,这里要在包裹一层子查询
		select -- 查询重复数据中,name, sex, age的值
			name, sex, age
		from user_info
		group by name, sex, age
		having count(*) > 1
	) as x
)
and id not in (
	select min_id from ( -- 删除的时候,这里要在包裹一层子查询
		select -- 查询重复数据中,最小的id值
			min(id) as min_id
		from user_info 
		group by name, sex, age
		having count(*) > 1
	) as y
);

注意:上面的删除语句中,我们在两个where条件中的子查询语句外面又包裹了一层子查询,即为上面SQL语句中的as x和as y两个查询语句,之所以包裹一层的原因是在程序如下的错误提示:

1093 - You can't specify target table 'user_info' 
for update in FROM clause, Time: 0.084000s

上述错误的原因是:修改一个表的时候子查询不能是这被修改的这个表,所以,我们的解决办法是,在子查询外面再套一层查询语句就可以了。

方法2:

delete from user_info 
where id not in(
	select min_id from (
		select 
			min(id) as min_id
		from user_info 
		group by name, sex, age
	) as x
);

注意:这里为了避免MySQL的1903错误,我们也在where条件的子查询中包裹了另外一个子查询,即上面SQL中as x查询语句。

上面相当于从全部数据中,排除数据,然后删除。相当于,一次性操作全表。我们可以加上范围(id between(min,max)),分批处理。

自联表

进行联表查询,inner join 同一张表,进行处理。会有重复,重复条数按组合排列来计算。(重复n条,则该条会有n*(n-1)/条,即组合排列。如果某条记录有3条重复的,那么查出来应该是组合 c3 2。4条重复的,则c4 2)


通过两个表关联的方式来删除数据,这个方式效率比较高,推荐使用这种方式。自己和自己关联,关联的条件就是我们判断数据是否为重复数据的key。除此之外,最重要的一个条件是:两个表的id关联条件,这个是删除保留数据的关键条件。

理解:首先name、sex、age相等,这个是查找重复的条件,而a.id=b.id 这样查找,条目数跟原有的表一样的条目,只是多了一些重复的列。而 a.id > b.id 相当于找到的是a.id较大的id,这样删除时,只保留最小的id。a.id < b.id 是找到较小的id,按a.id时,保留较小的id。另外,a.id中结果有重复。

查询重复的数据如下:

select a.*,b.* 
from user_info as a 
inner join user_info as b 
on a.name = b.name 
and a.sex = b.sex 
and a.age = b.age
and  a.id > b.id;

写法1:

删除重复数据SQL语句如下:

DELETE A .*
FROM
	user_info AS A
INNER JOIN user_info AS b ON A . NAME = b. NAME
AND A .sex = b.sex
AND A .age = b.age
AND A . ID > b. ID;

上面的写法,好像好像没有成功。

写法2:

查询待删除的重复数据SQL如下:

select * from user_info as a 
where a.id <> (
	select 
		min(b.id) 
	from user_info as b 
	where a.name = b.name
	and a.sex = b.sex
	and a.age = b.age
);

删除重复数据的SQL语句如下:

delete a.* from user_info as a
where a.id <> (
	select
		min(b.id)
	from (
		select * from user_info
	) as b
	where a.`name`= b.`name`
	and a.sex = b.sex
	and a.age = b.age
);

最后提醒一点: 在真正删除之前,记得对原数据备份一下。以便删除错误后,数据不能恢复回来。可以使用如下的语句来创建一个备份表,以便于在删除错误后,把数据恢复到原来的表中去。

create table user_info_bak as select * from user_info; --创建一个备份表
truncate table user_info; -- 清空原始表中的数据
insert into user_info select * from user_info_bak; -- 从备份表中把数据插入到原始表中

像上面这样操作,数据如果删除失误的时候,可以从user_info_bak中还原数据到user_info表中。

自己写的例子:

首先,表结构:

重复定义如下:ship_id、send_id、recv_id均相等,则认为重复了。

CREATE TABLE td_mail_02
(
    id bigserial,
    ship_id bigint NOT NULL,
    send_id varchar(24),
    recv_id varchar(24),
    kafka_time timestamp without time zone,
    insert_time timestamp without time zone,
    PRIMARY KEY ("id")
)
WITH (
    OIDS = FALSE
)
DISTRIBUTED BY (id);

查看需要删除的重复数据:

SELECT * from td_mail_02 where "id" BETWEEN 1 and 20  and id not in (
SELECT min("id") from td_mail_02 WHERE "id" BETWEEN 1 and 20  
GROUP BY
	ship_id,
	recv_id,
	send_id );

删除

DELETE from td_mail_02 where "id" BETWEEN 1 and 20  and id not in (
SELECT min("id") from td_mail_02 WHERE "id" BETWEEN 1 and 20  
GROUP BY
	ship_id,
	recv_id,
	send_id );

但是,此方法,只能保证局部BETWEEN 1 and 20 没有重复数据。如果是分批来删,只能保证每批里面没有重复数据,每披之间的数据无法保证是否重复。虽然有缺陷,但是按天删除重复数据,还是可以的。

自联表的查询

SELECT
	DISTINCT a."id"   -- 如果是* 显示全部
FROM
	td_mail_02 a
INNER JOIN td_mail_02 AS b ON a.ship_id = b.ship_id
AND a.recv_id = b.recv_id
AND a.send_id = b.send_id
AND a."id" > b."id"
WHERE
	a."id" BETWEEN 1
AND 8 

配合脚本执行,将上一步的代码写入。

delete from td_mail_02 where id in (上一步结构)

或者直接点:

DELETE from td_mail_02 where id in (
SELECT
	DISTINCT A."id"
FROM
	td_mail_02 A
INNER JOIN td_mail_02 AS b ON A .ship_id = b.ship_id
AND A .recv_id = b.recv_id
AND A .send_id = b.send_id
AND A ."id" > b."id"
WHERE
	A ."id" BETWEEN 1
AND 8 )

我突然想到,用脚本来保存结果,其实,我也能将其写到一张临时表里面。然后下次直接从表里面删除掉。然后分两个脚本来操作。