web-dev-qa-db-fra.com

MySQL supprime les doublons de la grande base de données rapidement

J'ai une grosse base de données MySQL (> Mil rangées) gâchée par des doublons. Je pense que cela pourrait représenter de 1/4 à 1/2 de la totalité de la base de données remplie avec eux .J'ai besoin de m'en débarrasser rapidement (je veux dire le temps d'exécution de la requête) . Voici à quoi cela ressemble:
id (index) | text1 | text2 | text3
La combinaison text1 & text2 doit être unique, s'il y a des doublons, une seule combinaison avec text3 NOT NULL doit rester. Exemple: 

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

...devient: 

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)

Les nouveaux identifiants ne font rien, ils ne dépendent pas des anciens identifiants de table.
J'ai essayé des choses comme:

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;

Ou SELECT DISTINCT et autres variations.
Bien qu’ils travaillent sur de petites bases de données, le temps d’exécution des requêtes sur la mienne est énorme (jamais jusqu’à la fin, en réalité;> 20 min)

Y at-il un moyen plus rapide de le faire? S'il vous plaît aidez-moi à résoudre ce problème.

69
bizzz

Je crois que cela suffira, en utilisant sur la clé dupliquée + 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;

Devrait être beaucoup plus rapide que tout ce qui nécessite un groupe par ou distinct, une sous-requête ou même un ordre. Cela n’a même pas besoin d’un portage de fichiers, ce qui réduira les performances d’une grande table temporaire. Il faudra quand même effectuer une analyse complète de la table d'origine, mais rien ne peut l'éviter.

146
ʞɔıu

J'ai trouvé ce code simple d'une ligne pour faire exactement ce dont j'avais besoin:

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

Tiré de: http://mediakey.dk/~cc/mysql-remove-duplicate-entries/

95
liorq
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
)

Ceci interroge tous les enregistrements, les groupes selon les champs de distinction et les ordres par ID (signifie que nous sélectionnons le premier enregistrement text3 non nul). Ensuite, nous sélectionnons les identifiants de ce résultat (ce sont de bons identifiants ... ils ne seront pas supprimés) et supprimons tous les identifiants qui NE SONT PAS.

Toute requête de ce type concernant l'ensemble de la table sera lente. Vous avez juste besoin de l'exécuter et de le laisser se dérouler afin de pouvoir l'éviter à l'avenir.

Après avoir résolu ce problème, je voudrais appliquer UNIQUE INDEX (text1, text2) à cette table. Empêcher la possibilité de doublons à l'avenir.

Si vous voulez aller "créez une nouvelle table et remplacez l'ancienne" route. Vous pouvez utiliser l’instruction select très interne pour créer votre instruction insert.

Spécifique à MySQL (en supposant que la nouvelle table s'appelle my_tbl2 et a exactement la même structure):

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

Voir MySQL INSERT ... SELECT pour plus d'informations.

12
Kevin Peno

supprimer les doublons sans retirer les clés étrangères

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);
8
gadelkareem

Si vous pouvez créer une nouvelle table, faites-le avec une clé unique sur les champs text1 + text2. Puis insérez dans la table en ignorant les erreurs (en utilisant la syntaxe INSERT IGNORE): 

select * from my_tbl order by text3 desc
  • Je pense que l'ordre par text3 desc mettra les NULL en dernier, mais vérifiez bien cela.

Les index sur toutes ces colonnes pourraient aider beaucoup, mais les créer maintenant pourrait être assez lent.

3
Scott Saunders

Pour les grandes tables avec peu de doublons, vous pouvez éviter de copier la table entière à un autre endroit. Une solution consiste à créer une table temporaire contenant les lignes que vous souhaitez conserver (pour chaque clé avec des doublons), puis à supprimer les doublons de la table d'origine. 

Un exemple est donné ici .

1
user1931858

vous pouvez supprimer toutes les entrées en double en utilisant cette requête simple . qui sélectionnera et supprimera tous les enregistrements en double.

 DELETE i1 
FROM TABLE i1
LEFT JOIN TABLE i2
  ON i1.id = i2.id
 AND i1.colo = i2.customer_invoice_id
 AND i1.id < i2.id
WHERE i2.customer_invoice_id IS NOT NULL
0
kamran Sheikh

Je sais que c’est un vieux fil, mais j’ai une méthode un peu {désordonnée} beaucoup plus rapide et personnalisable. En termes de rapidité, je dirais 10 secondes au lieu de 100 secondes (10: 1).

Ma méthode nécessitait tout ce que vous tentiez d'éviter désordonné:

  • Grouper par (et avoir)
  • groupe concat avec ORDER BY
  • 2 tables temporaires
  • en utilisant des fichiers sur le disque!
  • en quelque sorte (php?) supprimer le fichier après

Mais lorsque vous parlez de MILLIONS (ou dans mon cas, des dizaines de millions), cela en vaut la peine.

de toute façon ce n'est pas grand chose car les commentaires sont en portugais mais voici mon échantillon:

EDIT: si je reçois des commentaires, je vais expliquer plus en détail comment ça marche :)

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;
0
JDuarteDJ

Je n'ai pas beaucoup d'expérience avec MySQL. S'il a des fonctions analytiques, essayez:

 supprimer de my_tbl 
 où id dans (
 select id 
 à partir de (select id, row_number () 
 over (partition by text1, text2 order by text3 desc)) sous la forme rn 
 à partir de my_tbl .__// optional texte1 comme 'a%' */
) comme t2 
 où rn> 1 
) 

la clause optionnelle where permet de l'exécuter plusieurs fois, une par lettre, etc. Créez un index sur text1?

Avant de lancer ceci, vérifiez que "text desc" triera les nulls en dernier dans MySQL.

0
redcayuga