Удалить повторяющиеся строки в MySQL

299

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

id (Unique)
url (Unique)
title
company
site_id

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

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

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

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

  • 1
    Быстрый вопрос: всегда ли нужно, чтобы дубликат (title, company, site_id) не существовал? Если так, я бы установил ограничение в базе данных, чтобы заголовок, компания и site_id были уникальными. Что означало бы, что вам не понадобится процесс очистки. И это займет всего одну строку SQL.
  • 1
    Пожалуйста, обратитесь по этой ссылке на stackoverflow. Это сработало для меня как шарм.
Показать ещё 2 комментария
Теги:
duplicates
duplicate-removal

18 ответов

567
Лучший ответ

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

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

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

  • 7
    Интересно , но предположения, которые делает предложение IGNORE для удаления этих дубликатов, представляют собой проблему, которая может не соответствовать потребностям. Тебе не нравятся неправильные значения, усеченные до ближайшего приемлемого соответствия?
  • 0
    В данном конкретном случае это определенно верно. Соотношение заголовков и столбцов компании определенно имеет значение. Что именно означает неверные значения? Я чувствую запах другого вопроса ...
Показать ещё 15 комментариев
148

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

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

DELETE 'a'
FROM
    'jobs' AS 'a',
    'jobs' AS 'b'
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column 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 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';
  • 13
    Я пошел с этим решением, потому что я думаю, что это решение, где наиболее ясно, что делает код.
  • 5
    Это действительно должен быть принятый ответ, потому что (A) это стандартный SQL, а (B) ясно показывает, что происходит без магии за кулисами.
Показать ещё 9 комментариев
72

У 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
  • 3
    @andomar, это прекрасно работает, за исключением случаев, когда одно из полей в предложении where содержит нули. Пример: sqlfiddle.com/#!2/983f3/1
  • 5
    этот ответ должен быть принят
Показать ещё 4 комментария
36

Если оператор 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;
  • 1
    отлично работает, если у вас есть настройка innoDB с ограничением внешнего ключа.
  • 0
    @magdmartin, но не будут ли внешние ограничения препятствовать удалению таблицы?
Показать ещё 2 комментария
24

Существует другое решение:

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 ...
  • 3
    Чем это отличается от ответа @ rehriff, который он представил 6 месяцами ранее?
  • 0
    @LawrenceDol Я думаю, что это немного более читабельно, а также я думаю, что его ответ не был тем же самым, когда я отвечал, и я думаю, что его ответ был отредактирован.
18

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

⇒ Используя этот подход, регистры 1.6M были преобразованы в 6k менее чем за 200 секунд.

Chetan, следуя этому процессу, вы можете быстро и легко удалить все свои дубликаты и создать ограничение UNIQUE, выполнив:

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 DES заставляет последний идентификатор получать приоритет над остальными

Вариант выполнения некоторых задач для дубликатов, например, сохранение количества найденных дубликатов

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

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;
  • 0
    четко объяснил. Спасибо @Cesar. Эти запросы Alter действительно отлично работают вместо запроса на удаление, если мы хотим удалить повторяющуюся запись.
6

У меня есть этот snipet для 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)
  • 0
    Это не сработает, если в группе более двух дубликатов.
  • 11
    К сожалению, MySQL не позволяет вам выбирать из таблицы, которую вы удаляете из ERROR 1093: You can't specify target table 'Table' for update in FROM clause
Показать ещё 3 комментария
4

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

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);
  • 0
    Код ошибки: 1055. Выражение № 2 списка SELECT отсутствует в предложении GROUP BY и содержит неагрегированный столбец «dub.id», который функционально не зависит от столбцов в предложении GROUP BY; это несовместимо с sql_mode = only_full_group_by
  • 0
    Вы можете отключить «жесткий контроль» с помощью sql_mode, см. stackoverflow.com/questions/23921117/disable-only-full-group-by
4

Более быстрый способ - вставить разные строки во временную таблицу. Используя 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;  
  • 1
    Ваша четвертая строка должна сказать TRUNCATE TABLE tableName а пятая строка должна сказать INSERT INTO tableName SELECT * FROM tempTableName;
3

Я продолжаю посещать эту страницу в любое время, когда я google "удаляю дубликаты формы mysql", но для моих решений theIGNORE не работает, потому что у меня есть таблицы mysql InnoDB

этот код работает лучше в любое время

CREATE TABLE tableToclean_temp LIKE tableToclean;
ALTER TABLE tableToclean_temp ADD UNIQUE INDEX (fontsinuse_id);
INSERT IGNORE INTO tableToclean_temp SELECT * FROM tableToclean;
DROP TABLE tableToclean;
RENAME TABLE tableToclean_temp TO tableToclean;

tableToclean = имя таблицы, которую нужно очистить

tableToclean_temp = созданная и удаленная временная таблица

  • 0
    Чистое решение, также может сохранить резервную копию таблицы, если это необходимо. Благодарю.
2

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

-- Create temporary table

CREATE TABLE temp_table LIKE table1;

-- Add constraint
ALTER TABLE temp_table ADD UNIQUE(title, company,site_id);

-- Copy data
INSERT IGNORE INTO temp_table SELECT * FROM table1;

-- Rename and drop
RENAME TABLE table1 TO old_table1, temp_table TO table1;
DROP TABLE old_table1;
2

Решение, которое легко понять и работает без первичного ключа:

1) добавьте новый булевский столбец

alter table mytable add tokeep boolean;

2) добавьте ограничение на дублированные столбцы И новый столбец

alter table mytable add constraint preventdupe unique (mycol1, mycol2, tokeep);

3) установите для столбца boolean значение true. Это будет выполнено только на одной из дублированных строк из-за нового ограничения

update ignore mytable set tokeep = true;

4) удалите строки, которые не были помечены как tokeep

delete from mytable where tokeep is null;

5) отбросить добавленный столбец

alter table mytable drop tokeep;

Я предлагаю вам сохранить добавленное ограничение, чтобы новые дубликаты были предотвращены в будущем.

  • 0
    Это работало очень хорошо в MySQL 5.7, где принятое решение больше не работает
2

Это решение будет перемещать дубликаты в одну таблицу, а - в другую.

-- speed up creating uniques table if dealing with many rows
CREATE INDEX temp_idx ON jobs(site_id, company, title, location);

-- create the table with unique rows
INSERT jobs_uniques SELECT * FROM
    (
    SELECT * 
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) > 1
    UNION
    SELECT *
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) = 1
) x

-- create the table with duplicate rows
INSERT jobs_dupes 
SELECT * 
FROM jobs
WHERE id NOT IN
(SELECT id FROM jobs_uniques)

-- confirm the difference between uniques and dupes tables
SELECT COUNT(1)
AS jobs, 
(SELECT COUNT(1) FROM jobs_dupes) + (SELECT COUNT(1) FROM jobs_uniques)
AS sum
FROM jobs
  • 0
    Почему вы приняли профсоюз, а не просто SELECT * FROM jobs GROUP BY site_id, company, title, location ?
1

Удалите дублирующиеся строки с помощью инструкции DELETE JOIN MySQL предоставляет вам инструкцию DELETE JOIN, которую можно использовать для быстрого удаления дублирующихся строк.

Следующий оператор удаляет дублирующиеся строки и сохраняет самый высокий идентификатор:

DELETE t1 FROM contacts t1
    INNER JOIN
contacts t2 WHERE
t1.id < t2.id AND t1.email = t2.email;
0

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

DELETE t1 FROM tablename t1 INNER JOIN tablename t2 
WHERE t1.id < t2.id AND t1.column1 = t2.column1 AND t1.column2 = t2.column2;
-1

Мне нравится быть более конкретным в отношении того, какие записи я удаляю, так вот мое решение:

delete
from jobs c1
where not c1.location = 'Paris'
and  c1.site_id > 64218
and exists 
(  
select * from jobs c2 
where c2.site_id = c1.site_id
and   c2.company = c1.company
and   c2.location = c1.location
and   c2.title = c1.title
and   c2.site_id > 63412
and   c2.site_id < 64219
)
-2

Мне пришлось делать это с текстовыми полями и попадало в предел 100 байтов в индексе.

Я решил это, добавив столбец, выполнив хеш-метку md5 и выполнив alter.

ALTER TABLE table ADD `merged` VARCHAR( 40 ) NOT NULL ;
UPDATE TABLE SET merged` = MD5(CONCAT(`col1`, `col2`, `col3`))
ALTER IGNORE TABLE table ADD UNIQUE INDEX idx_name (`merged`);
-3

Вы можете легко удалить дубликаты записей из этого кода.

$qry = mysql_query("SELECT * from cities");
while($qry_row = mysql_fetch_array($qry))
{
$qry2 = mysql_query("SELECT * from cities2 where city = '".$qry_row['city']."'");

if(mysql_num_rows($qry2) > 1){
    while($row = mysql_fetch_array($qry2)){
        $city_arry[] = $row;

        }

    $total = sizeof($city_arry) - 1;
        for($i=1; $i<=$total; $i++){


            mysql_query( "delete from cities2 where town_id = '".$city_arry[$i][0]."'");

            }
    }
    //exit;
}
  • 2
    Это очень плохая форма - задачи базы данных должны выполняться в БД, где они выполняются намного быстрее, вместо того, чтобы постоянно отправлять данные между php / mysql, потому что вы знаете одно лучше, чем другое.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню