Bảng có 4 dòng, 2 khách. Xoá sai một câu lệnh là mất luôn cả hai. Đây là cách xoá an toàn.
Bài toán
Bảng nguoi_dung có email trùng do nhập từ nhiều nguồn. Cần biết email nào trùng, và xoá bớt chỉ giữ lại một bản.
Bước 1 — tìm giá trị trùng
SELECT email, COUNT(*) AS so_lan
FROM nguoi_dung
GROUP BY email
HAVING COUNT(*) > 1;
Đây là mẫu cố định: gom theo cột cần kiểm tra, lọc nhóm có nhiều hơn một dòng.
Bước 2 — xem đầy đủ các dòng trùng
SELECT *
FROM nguoi_dung
WHERE email IN (
SELECT email FROM nguoi_dung
GROUP BY email HAVING COUNT(*) > 1
)
ORDER BY email, id;
Bước 3 — xoá, giữ lại một bản
Cách 1: giữ id nhỏ nhất
DELETE FROM nguoi_dung
WHERE id NOT IN (
SELECT MIN(id) FROM nguoi_dung GROUP BY email
);
Cách 2: dùng ROW_NUMBER — linh hoạt hơn
WITH danh_so AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY email ORDER BY ngay_tao ASC, id ASC
) AS rn
FROM nguoi_dung
)
DELETE FROM nguoi_dung
WHERE id IN (SELECT id FROM danh_so WHERE rn > 1);
Cách này cho phép bạn chọn bản nào được giữ — bản tạo sớm nhất, bản đầy đủ thông tin nhất, tuỳ tiêu chí.
Luôn chạy SELECT trước DELETE
Đổi
DELETE FROM thành SELECT * FROM, xem kỹ danh sách sắp xoá, rồi mới đổi lại. Xoá xong không có nút hoàn tác.
Lỗi thường gặp
1. So trùng trên dữ liệu chưa chuẩn hoá
'An@X.com ' và 'an@x.com' là hai giá trị khác nhau. Gom theo LOWER(TRIM(email)) mới bắt được đủ.
2. Quên rằng MySQL không cho DELETE từ chính bảng đang SELECT
-- Lỗi 1093 trên MySQL
DELETE FROM nguoi_dung WHERE id NOT IN (SELECT MIN(id) FROM nguoi_dung GROUP BY email);
-- Bọc thêm một tầng để lách
DELETE FROM nguoi_dung WHERE id NOT IN (
SELECT * FROM (SELECT MIN(id) FROM nguoi_dung GROUP BY email) t
);
3. Ngăn trùng trong tương lai
CREATE UNIQUE INDEX ux_nguoi_dung_email ON nguoi_dung(LOWER(email));
Xoá xong mà không thêm ràng buộc thì tuần sau bạn lại làm lại từ đầu.
Luyện tập
- LeetCode 182 — Duplicate Emails
- LeetCode 196 — Delete Duplicate Emails
- LeetCode 1050 — Actors and Directors Who Cooperated At Least Three Times
- LeetCode 1729 — Find Followers Count
Tóm lại
GROUP BY … HAVING COUNT(*) > 1là mẫu tìm trùng chuẩn.ROW_NUMBERcho phép chọn bản nào được giữ lại.- Luôn chạy
SELECTtrước khi đổi thànhDELETE. - Chuẩn hoá chuỗi trước khi so trùng.
- Thêm
UNIQUE INDEXđể không phải làm lại.
Bạn vừa học xong tìm và xoá dữ liệu trùng
Sẵn sàng luyện tập chưa?
Làm bài tập tìm và xoá dữ liệu trùng trên dữ liệu thật, chấm điểm ngay khi bạn bấm chạy.
Bắt đầu luyện tập →