Supprimer les enregistrements en double basés sur plusieurs colonnes?

J’utilise Heroku pour héberger mon application Ruby on Rails et pour une raison ou une autre, je peux avoir des lignes en double.

Existe-t-il un moyen de supprimer les enregistrements en double sur la base de 2 critères ou plus mais de ne conserver qu’un enregistrement de cette collection en double?

Dans mon cas d’utilisation, j’ai une relation Make and Model pour les voitures dans ma firebase database.

Make Model --- --- Name Name Year Trim MakeId 

Je voudrais supprimer tous les enregistrements de modèle qui ont le même nom, année et taille, mais en conserver 1 (ce qui signifie que je n’en ai besoin qu’une seule fois). J’utilise la console Heroku afin de pouvoir exécuter facilement des requêtes d’enregistrement actives.

Aucune suggestion?

 class Model def self.dedupe # find all models and group them on keys which should be common grouped = all.group_by{|model| [model.name,model.year,model.sortingm,model.make_id] } grouped.values.each do |duplicates| # the first one we want to keep right? first_one = duplicates.shift # or pop for last one # if there are any more left, they are duplicates # so delete all of them duplicates.each{|double| double.destroy} # duplicates can now be destroyed end end end Model.dedupe 
  • Trouver tout
  • Groupez-les sur des clés dont vous avez besoin pour être uniques
  • Boucle sur les valeurs du modèle groupé du hachage
  • supprimer la première valeur car vous souhaitez conserver une copie
  • supprimer le rest

Si vos données de table utilisateur sont comme ci-dessous

 User.all => [ #, #, #, #, #, #] 1.9.2p290 :099 > 

Les identifiants d’e-mail sont en double, notre objective est donc de supprimer tous les identifiants de messagerie en double de la table utilisateur.

Étape 1:

Pour obtenir tous les identifiants des enregistrements de messagerie distincts.

 ids = User.select("MIN(id) as id").group(:email,:name).collect(&:id) => [15, 16, 18, 19, 17] 

Étape 2:

Pour supprimer les identifiants en double de la table utilisateur avec un identifiant d’enregistrement distinct.

Maintenant, le tableau des identifiants contient les identifiants suivants.

 [15, 16, 18, 19, 17] User.where("id NOT IN (?)",ids) # To get all duplicate records User.where("id NOT IN (?)",ids).destroy_all 

** RAILS 4 **

ActiveRecord 4 introduit la méthode .not qui vous permet d’écrire ce qui suit à l’étape 2:

 User.where.not(id: ids).destroy_all 

Semblable à la réponse de @Aditya Sanghi, mais cette méthode sera plus performante car vous ne sélectionnez que les doublons, plutôt que de charger tous les objects Model en mémoire et de les parcourir tous.

 # returns only duplicates in the form of [[name1, year1, sortingm1], [name2, year2, sortingm2],...] duplicate_row_values = Model.select('name, year, sortingm, count(*)').group('name, year, sortingm').having('count(*) > 1').pluck(:name, :year, :sortingm) # load the duplicates and order however you wantm and then destroy all but one duplicate_row_values.each do |name, year, sortingm| Model.where(name: name, year: year, sortingm: sortingm).order(id: :desc)[1..-1].map(&:destroy) end 

De plus, si vous ne voulez vraiment pas de données dupliquées dans cette table, vous souhaiterez probablement append un index unique multi-colonnes à la table, quelque chose comme:

 add_index :models, [:name, :year, :sortingm], unique: true, name: 'index_unique_models' 

Pour l’exécuter lors d’une migration, j’ai fini par faire comme suit (basé sur la réponse ci-dessus par @ aditya-sanghi)

 class AddUniqueIndexToXYZ < ActiveRecord::Migration def change # delete duplicates dedupe(XYZ, 'name', 'type') add_index :xyz, [:name, :type], unique: true end def dedupe(model, *key_attrs) model.select(key_attrs).group(key_attrs).having('count(*) > 1').each { |duplicates| dup_rows = model.where(duplicates.atsortingbutes.slice(key_attrs)).to_a # the first one we want to keep right? dup_rows.shift dup_rows.each{ |double| double.destroy } # duplicates can now be destroyed } end end 

Vous pouvez essayer ce qui suit: (basé sur les réponses précédentes)

 ids = Model.group('name, year, sortingm').pluck('MIN(id)') 

pour obtenir tous les enregistrements valides. Et alors:

 Model.where.not(id: ids).destroy_all 

pour supprimer les enregistrements inutiles. Et certainement, vous pouvez effectuer une migration qui ajoute un index unique pour les trois colonnes afin que cela soit appliqué au niveau de la firebase database:

 add_index :models, [:name, :year, :sortingm], unique: true 

Vous pouvez essayer cette requête SQL, pour supprimer tous les enregistrements en double mais le dernier

 DELETE FROM users USING users user WHERE (users.name = user.name AND users.year = user.year AND users.sortingm = user.sortingm AND users.id < user.id);