Удалить повторяющиеся строки в MySQL - PullRequest
337 голосов
/ 22 июля 2010

У меня есть таблица со следующими полями:

id (Unique)
url (Unique)
title
company
site_id

Теперь мне нужно удалить строки, имеющие одинаковые title, company and site_id. Один из способов сделать это будет использовать следующий SQL вместе со скриптом (PHP):

SELECT title, site_id, location, id, count( * ) 
FROM jobs
GROUP BY site_id, company, title, location
HAVING count( * ) >1

После выполнения этого запроса я могу удалить дубликаты, используя скрипт на стороне сервера.

Но я хочу знать, можно ли это сделать только с помощью SQL-запроса.

Ответы [ 23 ]

586 голосов
/ 22 июля 2010

Действительно простой способ сделать это - добавить индекс UNIQUE в 3 столбца. Когда вы пишете оператор ALTER, включите ключевое слово IGNORE. Вот так:

ALTER IGNORE TABLE jobs
ADD UNIQUE INDEX idx_name (site_id, title, company);

Это отбросит все повторяющиеся строки. Как дополнительное преимущество, будущие INSERTs, которые являются дубликатами, будут иметь ошибку. Как всегда, вы можете сделать резервную копию, прежде чем запускать что-то вроде этого ...

154 голосов
/ 08 августа 2014

Если вы не хотите изменять свойства столбца, используйте запрос ниже.

Поскольку у вас есть столбец с уникальными идентификаторами (например, auto_increment столбцы), вы можете использовать его для удаления дубликатов:

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND (`a`.`title` = `b`.`title` OR `a`.`title` IS NULL AND `b`.`title` IS NULL)
    AND (`a`.`company` = `b`.`company` OR `a`.`company` IS NULL AND `b`.`company` IS NULL)
    AND (`a`.`site_id` = `b`.`site_id` OR `a`.`site_id` IS NULL AND `b`.`site_id` IS NULL);

В MySQL вы можете еще больше упростить его с помощью NULL-безопасного оператора равенства (он же "оператор космического корабля" ):

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND `a`.`title` <=> `b`.`title`
    AND `a`.`company` <=> `b`.`company`
    AND `a`.`site_id` <=> `b`.`site_id`;
72 голосов
/ 22 июля 2010

MySQL имеет ограничения относительно ссылки на таблицу, из которой вы удаляете. Вы можете обойти это с помощью временной таблицы, например:

create temporary table tmpTable (id int);

insert  tmpTable
        (id)
select  id
from    YourTable yt
where   exists
        (
        select  *
        from    YourTabe yt2
        where   yt2.title = yt.title
                and yt2.company = yt.company
                and yt2.site_id = yt.site_id
                and yt2.id > yt.id
        );

delete  
from    YourTable
where   ID in (select id from tmpTable);

Из предложения Костаноса в комментариях:
Единственный медленный запрос выше - это DELETE, если у вас очень большая база данных. Этот запрос может быть быстрее:

DELETE FROM YourTable USING YourTable, tmpTable WHERE YourTable.id=tmpTable.id
40 голосов
/ 31 января 2013

Если оператор IGNORE не будет работать, как в моем случае, вы можете использовать следующий оператор:

CREATE TABLE your_table_deduped like your_table;
INSERT your_table_deduped SELECT * FROM your_table GROUP BY index1_id, index2_id;
RENAME TABLE your_table TO your_table_with_dupes;
RENAME TABLE your_table_deduped TO your_table;
#OPTIONAL
ALTER TABLE `your_table` ADD UNIQUE `unique_index` (`index1_id`, `index2_id`);
#OPTIONAL
DROP TABLE your_table_with_dupes;
24 голосов
/ 01 февраля 2015

Есть другое решение:

DELETE t1 FROM my_table t1, my_table t2 WHERE t1.id < t2.id AND t1.my_field = t2.my_field AND t1.my_field_2 = t2.my_field_2 AND ...
24 голосов
/ 20 ноября 2017

Удаление дубликатов в таблицах MySQL является распространенной проблемой, обычно это результат отсутствия ограничения, позволяющего избежать этих дубликатов заранее. Но эта общая проблема обычно связана с конкретными потребностями ... которые требуют определенных подходов. Подход должен отличаться в зависимости, например, от размера данных, дублируемой записи, которая должна быть сохранена (обычно первая или последняя), от того, есть ли индексы, которые нужно сохранить, или от того, хотим ли мы выполнить какие-либо дополнительные действия. действие на дублированные данные.

Есть также некоторые особенности самого MySQL, такие как невозможность ссылаться на одну и ту же таблицу по причине FROM при выполнении таблицы UPDATE (это вызовет ошибку MySQL # 1093). Это ограничение можно преодолеть, используя внутренний запрос с временной таблицей (как предложено в некоторых подходах выше). Но этот внутренний запрос не будет работать особенно хорошо при работе с большими источниками данных.

Тем не менее, существует лучший подход для удаления дубликатов, он эффективен и надежен, и его можно легко адаптировать к различным потребностям.

Общая идея состоит в том, чтобы создать новую временную таблицу, обычно добавляя уникальное ограничение, чтобы избежать дальнейших дубликатов, и вставлять данные из прежней таблицы в новую, одновременно заботясь о дубликатах. Этот подход основан на простых запросах MySQL INSERT, создает новое ограничение, чтобы избежать дальнейших дубликатов, и пропускает необходимость использования внутреннего запроса для поиска дубликатов и временной таблицы, которая должна храниться в памяти (таким образом, подходя также для больших источников данных).

Вот как это может быть достигнуто. Учитывая, что у нас есть таблица employee , со следующими столбцами:

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 добавляет уникальное ограничение к новой таблице tmp_eployee , чтобы избежать дальнейших дубликатов
  • Строка # 3 просматривает исходную таблицу employee по идентификатору, вставляя новые записи сотрудников в новую таблицу tmp_eployee , игнорируя при этом дублированные записи
  • Строка # 4 переименовывает таблицы, так что новая таблица employee содержит все записи без дубликатов, а резервная копия прежних данных сохраняется в таблице backup_employee

Используя этот подход, регистры 1.6M были преобразованы в 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 запрос INSERT INTO ... ON DUPLICATE KEY UPDATE используется для выполнения дополнительного обновления при обнаружении дубликата (в этом случае увеличение счетчика) Запрос INSERT INTO ... ON DUPLICATE KEY UPDATE может использоваться для выполнения различных типов обновлений для найденных дубликатов.

✔ Вариант для регенерации автоинкрементного поля id

Иногда мы используем автоинкрементное поле и, чтобы индекс был максимально компактным, мы можем воспользоваться удалением дубликатов для регенерацииавтоинкрементное поле в новой временной таблице.

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;
6 голосов
/ 22 июля 2010

У меня есть этот фрагмент запроса для SQLServer, но я думаю, что он может быть использован в других СУБД с небольшими изменениями:

DELETE
FROM Table
WHERE Table.idTable IN  (  
    SELECT MAX(idTable)
    FROM idTable
    GROUP BY field1, field2, field3
    HAVING COUNT(*) > 1)

Я забыл вам сказать, что этот запрос не удаляет строку с самым низкимидентификатор дублированных строк.Если это работает для вас, попробуйте этот запрос:

DELETE
FROM jobs
WHERE jobs.id IN  (  
    SELECT MAX(id)
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING COUNT(*) > 1)
4 голосов
/ 09 декабря 2016

Просто и быстро для всех случаев:

CREATE TEMPORARY TABLE IF NOT EXISTS _temp_duplicates AS (SELECT dub.id FROM table_with_duplications dub GROUP BY dub.field_must_be_uniq_1, dub.field_must_be_uniq_2 HAVING COUNT(*)  > 1);

DELETE FROM table_with_duplications WHERE id IN (SELECT id FROM _temp_duplicates);
4 голосов
/ 23 ноября 2016

Более быстрый способ - вставить отдельные строки во временную таблицу.Используя delete, мне потребовалось несколько часов, чтобы удалить дубликаты из таблицы из 8 миллионов строк.Используя вставку и отчетливый, это заняло всего 13 минут.

CREATE TABLE tempTableName LIKE tableName;  
CREATE INDEX ix_all_id ON tableName(cellId,attributeId,entityRowId,value);  
INSERT INTO tempTableName(cellId,attributeId,entityRowId,value) SELECT DISTINCT cellId,attributeId,entityRowId,value FROM tableName;  
TRUNCATE TABLE tableName;
INSERT INTO tableName SELECT * FROM tempTableName; 
DROP TABLE tempTableName;  
3 голосов
/ 14 января 2019

Я нашел простой способ. (держать последнюю информацию)

DELETE t1 FROM tablename t1 INNER JOIN tablename t2 
WHERE t1.id < t2.id AND t1.column1 = t2.column1 AND t1.column2 = t2.column2;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...