1. 程式人生 > 資料庫 >MySQL 如何查詢並刪除重複記錄的實現

MySQL 如何查詢並刪除重複記錄的實現

大家好,我是隻談技術不剪髮的 Tony 老師。由於一些歷史原因或者誤操作,可能會導致資料表中存在重複的記錄;今天我們就來談談如何查詢 MySQL 表中的重複資料以及如何刪除這些重複的記錄。

建立示例表

首先建立一個示例表 people 並生成一些資料:

drop table if exists people;
create table people (
 id int auto_increment primary key,name varchar(50) not null,email varchar(100) not null
);

insert into people(name,email)
values ('張三','[email protected]'),('李四','[email protected]'),('王五','[email protected]'),('李斯','[email protected]');

select * from people;
id|name |email   |
--|------|-----------------|
 1|張三 |[email protected]|
 2|李四 |[email protected] |
 3|王五 |[email protected] |
 4|李斯 |[email protected] |
 5|王五 |[email protected] |
 6|王五 |[email protected] |

其中,2 和 4 的 email 欄位存在重複資料;3、5 和 6 的 name 和 email 欄位存在重複資料。

此時,如果我們想要為 email 建立一個唯一約束,將會返回錯誤:

alter table people add constraint uk_people_email unique key (email);
ERROR 1062 (23000): Duplicate entry '[email protected]' for key 'people.uk_people_email'

顯然,我們必須找出並刪除 email 欄位中的重複記錄才能建立唯一約束。

查詢單個欄位中的重複資料

如果想要找出 email 重複的資料,可以基於該欄位進行分組統計,並且返回行數大於 1 的分組:

select email,count(email)
from people
group by email
having count(email) > 1;
email   |count(email)|
---------------|------------|
[email protected] |   2|
[email protected]|   3|

查詢結果顯示有兩個郵箱地址存在重複情況。如果想要檢視完整的重複資料,可以使用子查詢或者連線查詢:

select *
from people
where email in (
  select email
  from people
  group by email
  having count(email) > 1)
order by email;
id|name |email   |
--|------|---------------|
 2|李四 |[email protected] |
 4|李斯 |[email protected] |
 3|王五 |[email protected]|
 5|王五 |[email protected]|
 6|王五 |[email protected]|

select p.*
from people p
join (
 select email
 from people
 group by email
 having count(email) > 1
) d on p.email = d.email
order by email;
id|name |email   |
--|------|---------------|
 2|李四 |[email protected] |
 4|李斯 |[email protected] |
 3|王五 |[email protected]|
 5|王五 |[email protected]|
 6|王五 |[email protected]|

另一種查詢重複記錄的方法就是直接使用自連線查詢和 distinct 操作符,例如:

select distinct p.*
from people p
join people d on p.email = d.email
where p.id <> d.id
order by p.email;
id|name |email   |
--|------|---------------|
 4|李斯 |[email protected] |
 2|李四 |[email protected] |
 6|王五 |[email protected]|
 5|王五 |[email protected]|
 3|王五 |[email protected]|

注意,不能省略 distinct,否則會某些資料(3、5、6)會返回多次。

查詢多個欄位中的重複資料

如果我們想要找出 name 和 email 欄位都重複的資料,實現方式也類似:

select *
from people
where (name,email) in (
  select name,email
  from people
  group by name,email
  having count(1) > 1)
order by email;
id|name |email   |
--|------|---------------|
 3|王五 |[email protected]|
 5|王五 |[email protected]|
 6|王五 |[email protected]|

select distinct p.*
from people p
join people d on p.name = d.name and p.email = d.email
where p.id <> d.id
order by email;
id|name |email   |
--|------|---------------|
 6|王五 |[email protected]|
 5|王五 |[email protected]|
 3|王五 |[email protected]|

只有當 name 和 email 都相同時才是重複資料,所以 2 和 4 不是重複記錄。

刪除重複資料

找出重複資料之後,需要解決的就是如何刪除了,通常我們需要保留其中的一條記錄。

使用 DELETE FROM 刪除重複資料

假如我們想要刪除 email 重複的記錄,只保留其中一條,可以使用 DELETE FROM 語句實現:

delete p
from people p
join people d on p.email = d.email and p.id < d.id;

delete 語句通過連線找出需要刪除的記錄,以上示例保留了重複資料中的最大 id 對應的資料行。再次查詢 people 表:

select * from people;
id|name |email   |
--|------|-----------------|
 1|張三 |[email protected]|
 4|李斯 |[email protected] |
 6|王五 |[email protected] |

想一想,如果想要保留重複資料中 id 最小的資料應該怎麼實現呢?

利用子查詢刪除重複資料

通過子查詢可以找出需要保留的資料,然後刪除其他的資料:

delete
from people
where id not in (
  select max(id)
  from people
  group by email
  );

在執行上面的語句之前,記得重新建立 people 表並生成測試資料。

通過中間表刪除重複資料

通過使用中間表也可以實現重複記錄的刪除,例如:

-- 建立中間表
create table people_temp like people;

-- 複製需要保留的資料行
insert into people_temp(id,name,email)
select id,email
from people
where id in (
  select max(id)
  from people
  group by email
  );

--刪除原表
drop table people;

-- 將中間表重新命名為原表
alter table people_temp rename to people;

在執行上面的語句之前,記得重新建立 people 表並生成測試資料。

這種方式需要注意的一個問題就是 create table … like 語句不會複製原表上的外來鍵約束,需要手動新增。

利用視窗函式刪除重複資料

ROW_NUMBER() 是 MySQL 8.0 中新增的視窗函式,可以用於將資料進行分組,然後為每一條資料分配一個唯一的數字編號。例如:

select id,email,row_number() over (partition by email order by id) as row_num 
from people;
id|name |email   |row_num|
--|------|-----------------|-------|
 2|李四 |[email protected] |  1|
 4|李斯 |[email protected] |  2|
 3|王五 |[email protected] |  1|
 5|王五 |[email protected] |  2|
 6|王五 |[email protected] |  3|
 1|張三 |[email protected]|  1|

以上語句基於 email 分組(partition by email),同時按照 id 進行排序(order by id),然後為每個組內的資料分配一個編號;如果編號大於 1 就意味著存在重複的資料。

📝除了 ROW_NUMBER() 之外,RANK() 或者 DENSE_RANK() 函式也可以實現以上功能。關於視窗函式的介紹和使用案例,可以參考這篇文章。

基於該查詢結果可以刪除重複的記錄:

delete
from people
where id in (
 select id
 from (
  select id,row_number() over (partition by email order by id desc) as row_num 
  from people) d
 where row_num > 1);

在執行上面的語句之前,記得重新建立 people 表並生成測試資料。

基於多個欄位的重複資料刪除方法和單個欄位非常類似,大家可以自行嘗試,也歡迎留言討論!

總結

本文介紹瞭如何在 MySQL 中查詢並刪除重複記錄,包括使用 GROUP BY 分組、子查詢或者連線查詢等方法查詢單個欄位或者多個欄位中的重複資料,以及使用 DELETE FROM 語句、子查詢、中間表和視窗函式等方法實現重複資料的刪除。更多相關MySQL 查詢並刪除重複記錄內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!