Удаление дубликатов в таблицах MySQL - это распространенная проблема, которая обычно является результатом отсутствия ограничения, позволяющего избежать этих дубликатов заранее. Но эта общая проблема обычно связана с конкретными потребностями ... которые требуют определенных подходов. Подход должен отличаться в зависимости, например, от размера данных, дублируемой записи, которая должна быть сохранена (обычно первая или последняя), от того, есть ли индексы, которые нужно сохранить, или от того, хотим ли мы выполнить какие-либо дополнительные действия. действие на дублированные данные.
Есть также некоторые особенности самого MySQL, такие как невозможность ссылки на ту же таблицу по причине FROM при выполнении таблицы UPDATE (это вызовет ошибку MySQL # 1093). Это ограничение можно преодолеть, используя внутренний запрос с временной таблицей (как предложено в некоторых подходах выше). Но этот внутренний запрос не будет работать особенно хорошо при работе с большими источниками данных.
Тем не менее, существует лучший подход для удаления дубликатов, он эффективен и надежен, и его можно легко адаптировать к различным потребностям.
Общая идея состоит в том, чтобы создать новую временную таблицу, обычно добавляя уникальное ограничение, чтобы избежать дальнейших дубликатов, и вставлять данные из прежней таблицы в новую, одновременно заботясь о дубликатах. Этот подход основан на простых запросах MySQL INSERT, создает новое ограничение, чтобы избежать дальнейших дубликатов, и пропускает необходимость использования внутреннего запроса для поиска дубликатов и временной таблицы, которая должна храниться в памяти (таким образом, подходя также для больших источников данных).
Вот как это может быть достигнуто. Учитывая, что у нас есть таблица сотрудников , со следующими столбцами:
employee (id, first_name, last_name, start_date, ssn)
Чтобы удалить строки с повторяющимся столбцом ssn и сохранить только первую найденную запись, можно выполнить следующий процесс:
-- create a new tmp_eployee table
CREATE TABLE tmp_employee LIKE employee;
-- add a unique constraint
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
-- scan over the employee table to insert employee entries
INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id;
-- rename tables
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
Техническое объяснение
- Строка # 1 создает новую таблицу tmp_eployee с точно такой же структурой, что и таблица employee
- Строка № 2 добавляет ограничение UNIQUE к новой таблице tmp_eployee, чтобы избежать дальнейших дубликатов
- Строка № 3 просматривает исходную таблицу сотрудников по идентификатору, вставляя новые записи сотрудников в новую таблицу tmp_eployee , игнорируя при этом дублированные записи.
- Строка № 4 переименовывает таблицы, так что новая таблица сотрудников содержит все записи без дубликатов, а резервная копия прежних данных хранится в таблице backup_employee.
⇒ Используя этот подход, 1.6M регистры были преобразованы в 6 КБ менее чем за 200 с.
Четан , следуя этому процессу, вы можете быстро и легко удалить все свои дубликаты и создать УНИКАЛЬНОЕ ограничение, запустив:
CREATE TABLE tmp_jobs LIKE jobs;
ALTER TABLE tmp_jobs ADD UNIQUE(site_id, title, company);
INSERT IGNORE INTO tmp_jobs SELECT * FROM jobs ORDER BY id;
RENAME TABLE jobs TO backup_jobs, tmp_jobs TO jobs;
Конечно, этот процесс может быть дополнительно изменен, чтобы адаптировать его для различных нужд при удалении дубликатов. Вот несколько примеров.
✔ Вариант для сохранения последней записи вместо первой
Иногда нам нужно сохранить последнюю дублированную запись вместо первой.
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id DESC;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- В строке № 3 предложение DESC ORDER BY id создает последние идентификаторы, которые получают приоритет над остальными.
✔ Вариант выполнения некоторых задач с дубликатами, например, ведение учета найденных дубликатов.
Иногда нам нужно выполнить некоторую дальнейшую обработку найденных дублированных записей (например, вести подсчет дубликатов).
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;
INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- В строке # 3, новый столбец n_duplicates создается
- В строке # 4 запрос INSERT INTO ... ON DUPLICATE KEY UPDATE используется для выполнения дополнительного обновления при обнаружении дубликата (в этом случае увеличивается счетчик). Запрос INSERT INTO ... ON DUPLICATE KEY UPDATE может быть используется для выполнения различных типов обновлений для найденных дубликатов.
✔ Вариант для регенерации идентификатора автоинкрементного поля
Иногда мы используем автоинкрементное поле и, чтобы сохранить индекс как можно более компактным, мы можем воспользоваться удалением дубликатов для регенерации автоинкрементного поля в новой временной таблице.
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
INSERT IGNORE INTO tmp_employee SELECT (first_name, last_name, start_date, ssn) FROM employee ORDER BY id;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- В строке № 3 вместо выбора всех полей в таблице поле id пропускается, так что механизм БД генерирует новое автоматически
✔ дальнейшие изменения
Многие дополнительные модификации также возможны в зависимости от желаемого поведения. В качестве примера, следующие запросы будут использовать вторую временную таблицу, чтобы, кроме 1) сохранить последнюю запись вместо первой; и 2) увеличить счетчик найденных дубликатов; также 3) восстановить автоматически инкрементный идентификатор поля, сохраняя порядок ввода, как это было на предыдущих данных.
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;
INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id DESC ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;
CREATE TABLE tmp_employee2 LIKE tmp_employee;
INSERT INTO tmp_employee2 SELECT (first_name, last_name, start_date, ssn) FROM tmp_employee ORDER BY id;
DROP TABLE tmp_employee;
RENAME TABLE employee TO backup_employee, tmp_employee2 TO employee;