Видалення дублікатів у таблицях 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 з точно такою ж структурою, як таблиця службовців
- Рядок №2 додає Унікальне обмеження до нової таблиці tmp_eployee, щоб уникнути подальших дублікатів
- Рядок №3 сканує над початковою таблицею службовців за допомогою id, вставляючи нові записи співробітників у нову таблицю tmp_eployee , ігноруючи повторювані записи
- Рядок №4 перейменовує таблиці, так що нова таблиця співробітників зберігає всі записи без дублікатів, а резервна копія колишніх даних зберігається в таблиці backup_employee
⇒ Використовуючи цей підхід, 1,6М регістри були перетворені на 6k менш ніж за 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 пункт ORDER BY id DESC робить останній ідентифікатор, щоб отримати пріоритет перед іншими
Варіант виконання деяких завдань над дублікатами, наприклад, підрахунок знайдених дублікатів
Іноді нам потрібно здійснити деяку подальшу обробку знайдених дублюваних записів (наприклад, збереження кількості дублікатів).
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 запит ВСТАВИТИ У ДУПЛІКАТИ КЛЮЧОВОГО ОБНОВЛЕННЯ запит використовується для здійснення додаткового оновлення, коли буде знайдено дублікат (у цьому випадку збільшується лічильник) ВСТАВКА У ВІД ... НА ДУПЛІКАТИ КЛЮЧНО ОНОВЛЕННЯ запит може бути використовується для виконання різних типів оновлень для знайдених дублікатів.
✔ Варіант для відновлення ідентифікатора автоматичного збільшення поля
Іноді ми використовуємо поле з автоматичним збільшенням, і для того, щоб індекс був максимально компактним, ми можемо скористатись видаленням дублікатів для відновлення авто-додаткового поля в новій тимчасовій таблиці.
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;