2009-10-30 2 views
68

У меня есть большие (> строки Mil) база данных MySQL, испорченная дубликатами. Я думаю, что это может быть от 1/4 до 1/2 от всего db, заполненного ими. Мне нужно быстро избавиться от них (я имею в виду время выполнения запроса). Вот как это выглядит:
ID (указатель) | text1 | text2 | text3
text1 & text2 комбинация должна быть уникальной, , если есть какие-либо дубликаты, только одна комбинация с текстом3 NOT NULL должна оставаться. Пример:MySQL удаляет дубликаты из большой базы данных быстро

1 | abc | def | NULL 
2 | abc | def | ghi 
3 | abc | def | jkl 
4 | aaa | bbb | NULL 
5 | aaa | bbb | NULL 

... становится:

1 | abc | def | ghi #(doesn't realy matter id:2 or id:3 survives) 
2 | aaa | bbb | NULL #(if there's no NOT NULL text3, NULL will do) 

Новые Идентификаторы холодной быть ничего, они не зависят от старой таблицы идентификаторов.
Я пытался что-то вроде:

CREATE TABLE tmp SELECT text1, text2, text3 
FROM my_tbl; 
GROUP BY text1, text2; 
DROP TABLE my_tbl; 
ALTER TABLE tmp RENAME TO my_tbl; 

Или SELECT DISTINCT и другие варианты.
В то время как они работают с небольшими базами данных, время выполнения запроса на шахте просто огромно (никогда не было до конца, на самом деле;> 20 минут)

Есть ли более быстрый способ сделать это? Пожалуйста, помогите мне решить эту проблему.

+2

Просьба уточнить: а) нумеруется нумеру поле идентификатора? б) какое количество или соотношение дубликатов мы ожидаем? (полезно решить работу на месте или создать новую таблицу). c) какие индексы существуют в текущей таблице. – mjv

+0

a) Перенумерация поля id не требуется b) моя оценка: от 1/4 до 1/2 от db являются дубликатами. C) id является единственным индексом. Я отредактирую вопрос соответствующим образом. – bizzz

ответ

145

Я считаю, что это будет делать это, используя дубликат ключа + IFNULL():

create table tmp like yourtable; 

alter table tmp add unique (text1, text2); 

insert into tmp select * from yourtable 
    on duplicate key update text3=ifnull(text3, values(text3)); 

rename table yourtable to deleteme, tmp to yourtable; 

drop table deleteme; 

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

+0

Спасибо, это работает! 1,2 млн. Рядов составляли 0,6 мила за 60 минут, так что около 10000 строк за минуту. Спасибо за четкое объяснение! :) – bizzz

+0

Это была большая помощь. Спасибо – rpearce

+15

@ ʞɔıu (upsideDownNick) прост и эффективен. для тех, кто не заботится о том, что text3 не является нулевой частью, вы можете использовать INSERT IGNORE (не рассматривая часть ON DUPLICATE UPDATE), и mysql будет игнорировать ошибки и вставлять только первое отдельное значение, которое он находит (игнорируя последующие дубликаты). –

3

Если вы можете создать новую таблицу, сделайте это с помощью уникального ключа в полях text1 + text2. Затем вставить в таблицу игнорирования ошибок (с помощью INSERT IGNORE синтаксис):

select * from my_tbl order by text3 desc 
  • Я думаю, что заказ по text3 убыв поместит NULLs последним, но дважды проверьте.

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

+0

Он поместит nulls последним, но не удовлетворяет запросу, которое было «сохранить первый, у которого нет значения null в text3». для этого вам нужно будет заказать по ID ASC и добавить WHERE text3 НЕ НУЛЛ к вашему заявлению. –

+0

Это хороший момент. Однако это требование противоречит его образцу: 2 | aaa | bbb | NULL Возможно, он скажет нам, чего он действительно хочет. –

+0

Я перечитал его просьбу. Похоже, что ему все равно, если есть ненулевые ненулевые значения. Таким образом, ваш пример будет хорошо. :) –

12
DELETE FROM dups 
WHERE id NOT IN(
    SELECT id FROM (
     SELECT DISTINCT id, text1, text2 
      FROM dups 
     GROUP BY text1, text2 
     ORDER BY text3 DESC 
    ) as tmp 
) 

Это запрашивает все записи, группы различения полей и приказы по ID (значит мы выбираем первый не нулевую запись text3). Затем мы выбираем id из этого результата (это хорошие идентификаторы ... они не будут удалены) и удалите все идентификаторы, которые НЕ являются таковыми.

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

После того, как вы сделали это «исправление», я применил бы UNIQUE INDEX (text1, text2) к этой таблице. Чтобы предотвратить возможность дублирования в будущем.

Если вы хотите перейти на «создать новый стол и заменить старый». Вы можете использовать сам внутренний оператор select, чтобы создать инструкцию insert.

MySQL специфические (предполагается, что новая таблица с именем my_tbl2 и имеет точно такую ​​же структуру):

INSERT INTO my_tbl2 
    SELECT DISTINCT id, text1, text2, text3 
      FROM dups 
     GROUP BY text1, text2 
     ORDER BY text3 DESC 

См MySQL INSERT ... SELECT для получения дополнительной информации.

+0

Извините, что оба предложения удаляют дубликаты, но не выбирая правильное поле text3, чтобы выжить (NULL остаются, пока нет альтернатив NULL) – bizzz

0

У меня нет большого опыта работы с MySQL.Если у него есть аналитические функции попробовать:

 
delete from my_tbl 
where id in (
    select id 
     from (select id, row_number() 
          over (partition by text1, text2 order by text3 desc) as rn 
       from my_tbl 
       /* optional: where text1 like 'a%' */ 
      ) as t2 
     where rn > 1 
    ) 

опциональных где положение делает средство вы должны запустить его несколько раз, один для каждой буквы, и т.д. Создание индекса на text1?

Прежде чем запускать это, подтвердите, что «text desc» будет сортировать значения null в MySQL.

+0

Код ошибки: 1064 рядом '(раздел by ...' – bizzz

+0

Я думаю, MySql doesn ' .. т есть аналитические функции, я попробую еще раз позже – redcayuga

+0

вы можете запустить: создать таблицу Dups в SELECT, text1, text2 , не более (случай, когда text3 равно нулю, то 1 еще 0), как has_null3 , макс (случай, когда Text3 не равно нулю, то 1 еще 0) в качестве has_not_null3 , мин (случай, когда Text3 не равно нулю, то идентификатор еще нуль) в качестве pref_id ОТ my_tbl GROUP BY text1, текст2 , имеющий отсчет (*)> 1 Это даст нам список дублированных текстов1/2 и некоторых «предпочтительных» идентификаторов. Если это займет слишком много времени, и, вероятно, будет добавлено «где text1 нравится«% »или что-то в этом роде. – redcayuga

95

Найдено этот простой код 1 строки, чтобы делать то, что нужно:

ALTER IGNORE TABLE dupTest ADD UNIQUE INDEX(a,b); 

Взято из: http://mediakey.dk/~cc/mysql-remove-duplicate-entries/

+7

Похож на ошибку MySQL, препятствующую запрос (особенно '' IGNORE') от работы: Код ошибки: 1062 Дублируемая запись 'abc-def' для ключевого слова 'text1' – bizzz

+4

Не понимаю, как это работает! – 472084

+0

Спасибо Iiorq за это – Mohan

8

удалить дубликаты, не удаляя внешние ключи

create table tmp like mytable; 
ALTER TABLE tmp ADD UNIQUE INDEX(text1, text2, text3, text4, text5, text6); 
insert IGNORE into tmp select * from mytable; 
delete from mytable where id not in (select id from tmp); 
+1

благодарит за это решение, оно отлично работает! – MiQUEL

+1

Это должен быть правильный ответ. Простой и работоспособный. – PKHunter

1

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

Приводится пример here.

0

Я знаю, что это старая нить, но у меня есть несколько messy метод, который намного быстрее и настраивается, с точки зрения скорости я бы сказал 10 секунд вместо 100 секунд (10: 1).

Мой метод не требует, чтобы все, что грязный вещи вы пытались избежать:

  • группы по (и Having)
  • группа CONCAT с ORDER BY
  • 2 временных таблиц
  • с использованием файлы на диске!
  • какое-то образом (PHP?) Удаление файла после

Но когда вы говорите о МИЛЛИОНАХ (или в моем случае десятки миллионов), это стоит.

все равно его не так много, потому что комментарии в португальский, но вот мой пример:

EDIT: если я получаю комментарии я объясню далее, как это работает :)

START TRANSACTION; 

DROP temporary table if exists to_delete; 

CREATE temporary table to_delete as (
    SELECT 
     -- escolhe todos os IDs duplicados menos os que ficam na BD 
     -- A ordem de escolha dos IDs é dada por "ORDER BY campo_ordenacao DESC" em que o primeiro é o que fica 
     right(
      group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ','), 
      length(group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ',')) 
       - locate(",",group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ',')) 
     ) as ids, 

     count(*) as c 

    -- Tabela a eliminar duplicados 
    FROM teste_dup 

    -- campos a usar para identificar duplicados 
    group by test_campo1, test_campo2, teste_campoN 
    having count(*) > 1 -- é duplicado 
); 

-- aumenta o limite desta variável de sistema para o máx 
SET SESSION group_concat_max_len=4294967295; 

-- envia os ids todos a eliminar para um ficheiro 
select group_concat(ids SEPARATOR ',') from to_delete INTO OUTFILE 'sql.dat'; 

DROP temporary table if exists del3; 
create temporary table del3 as (select CAST(1 as signed) as ix LIMIT 0); 

-- insere os ids a eliminar numa tabela temporaria a partir do ficheiro 
load data infile 'sql.dat' INTO TABLE del3 
LINES TERMINATED BY ','; 

alter table del3 add index(ix); 

-- elimina os ids seleccionados 
DELETE teste_dup -- tabela 
from teste_dup -- tabela 

join del3 on id=ix; 

COMMIT; 
Смежные вопросы