J'utilise Heroku pour héberger mon Ruby on Rails application 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 en fonction de 2 critères ou plus, mais de conserver un seul enregistrement de cette collection en double?
Dans mon cas d'utilisation, j'ai une relation Marque et Modèle pour les voitures dans ma base de données.
Make Model
--- ---
Name Name
Year
Trim
MakeId
Je voudrais supprimer tous les enregistrements de modèle qui ont le même nom, l'année et le trim, mais conserver 1 de ces enregistrements (ce qui signifie que j'ai besoin de l'enregistrement mais une seule fois). J'utilise la console Heroku pour 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.trim,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
Si vos données de table utilisateur comme ci-dessous
User.all =>
[
#<User id: 15, name: "a", email: "[email protected]", created_at: "2013-08-06 08:57:09", updated_at: "2013-08-06 08:57:09">,
#<User id: 16, name: "a1", email: "[email protected]", created_at: "2013-08-06 08:57:20", updated_at: "2013-08-06 08:57:20">,
#<User id: 17, name: "b", email: "[email protected]", created_at: "2013-08-06 08:57:28", updated_at: "2013-08-06 08:57:28">,
#<User id: 18, name: "b1", email: "[email protected]", created_at: "2013-08-06 08:57:35", updated_at: "2013-08-06 08:57:35">,
#<User id: 19, name: "b11", email: "[email protected]", created_at: "2013-08-06 09:01:30", updated_at: "2013-08-06 09:01:30">,
#<User id: 20, name: "b11", email: "[email protected]", created_at: "2013-08-06 09:07:58", updated_at: "2013-08-06 09:07:58">]
1.9.2p290 :099 >
Les identifiants des e-mails sont en double, notre objectif est donc de supprimer tous les identifiants des e-mails en double de la table des utilisateurs.
É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 des utilisateurs avec un identifiant d'enregistrements de messagerie distinct.
Le tableau ids contient maintenant 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 présente le .not
méthode 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 chaque objet Model en mémoire puis de répéter sur chacun d'eux.
# returns only duplicates in the form of [[name1, year1, trim1], [name2, year2, trim2],...]
duplicate_row_values = Model.select('name, year, trim, count(*)').group('name, year, trim').having('count(*) > 1').pluck(:name, :year, :trim)
# load the duplicates and order however you wantm and then destroy all but one
duplicate_row_values.each do |name, year, trim|
Model.where(name: name, year: year, trim: trim).order(id: :desc)[1..-1].map(&:destroy)
end
De plus, si vous ne voulez vraiment pas de données en double dans cette table, vous souhaiterez probablement ajouter un index unique à plusieurs colonnes à la table, quelque chose comme:
add_index :models, [:name, :year, :trim], unique: true, name: 'index_unique_models'
Vous pouvez essayer ce qui suit: (basé sur les réponses précédentes)
ids = Model.group('name, year, trim').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 base de données:
add_index :models, [:name, :year, :trim], unique: true
Pour l'exécuter sur une migration, j'ai fini par faire comme suit (basé sur le 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.attributes.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