我正在使用Heroku来托管我的Ruby on Rails应用程序,出于某种原因,我可能会有一些重复的行。
有没有办法根据2个或更多条件删除重复记录,但只记录该重复集合的1条记录?
在我的用例中,我的数据库中有汽车的制造和模型关系。
Make Model
--- ---
Name Name
Year
Trim
MakeId
我想删除所有具有相同名称,年份和修剪的模型记录,但保留其中一条记录(意思是,我需要记录但只有一次)。我正在使用Heroku控制台,因此我可以轻松地运行一些活动的记录查询。
有什么建议吗?
答案 0 :(得分:130)
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
答案 1 :(得分:49)
如果你的用户表数据如下
User.all =>
[
#<User id: 15, name: "a", email: "a@gmail.com", created_at: "2013-08-06 08:57:09", updated_at: "2013-08-06 08:57:09">,
#<User id: 16, name: "a1", email: "a@gmail.com", created_at: "2013-08-06 08:57:20", updated_at: "2013-08-06 08:57:20">,
#<User id: 17, name: "b", email: "b@gmail.com", created_at: "2013-08-06 08:57:28", updated_at: "2013-08-06 08:57:28">,
#<User id: 18, name: "b1", email: "b1@gmail.com", created_at: "2013-08-06 08:57:35", updated_at: "2013-08-06 08:57:35">,
#<User id: 19, name: "b11", email: "b1@gmail.com", created_at: "2013-08-06 09:01:30", updated_at: "2013-08-06 09:01:30">,
#<User id: 20, name: "b11", email: "b1@gmail.com", created_at: "2013-08-06 09:07:58", updated_at: "2013-08-06 09:07:58">]
1.9.2p290 :099 >
电子邮件ID是重复的,因此我们的目标是从用户表中删除所有重复的电子邮件ID。
第1步:
获取所有不同的电子邮件记录ID。
ids = User.select("MIN(id) as id").group(:email,:name).collect(&:id)
=> [15, 16, 18, 19, 17]
第2步:
使用不同的电子邮件记录ID从用户表中删除重复的ID。
现在,ids数组包含以下ID。
[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引入了.not
方法,允许您在步骤2中编写以下内容:
User.where.not(id: ids).destroy_all
答案 2 :(得分:11)
与@Aditya Sanghi的答案类似,但这种方式会更高效,因为您只选择重复项,而不是将每个Model对象加载到内存中,然后迭代所有这些。
# 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
此外,如果您真的不想在此表中重复数据,您可能希望向表中添加多列唯一索引,类似于以下内容:
add_index :models, [:name, :year, :trim], unique: true, name: 'index_unique_models'
答案 3 :(得分:5)
您可以尝试以下操作:(基于之前的答案)
foo_bar
获取所有有效记录。然后:
ids = Model.group('name, year, trim').pluck('MIN(id)')
删除不需要的记录。当然,您可以进行迁移,为三列添加唯一索引,以便在数据库级别强制执行:
Model.where.not(id: ids).destroy_all
答案 4 :(得分:3)
要在迁移中运行它,我最终会执行以下操作(基于@ {aditya-sanghi的answer above)
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
答案 5 :(得分:0)
基于@aditya-sanghi's answer,并提供了一种更有效的方法来使用SQL查找重复项。
将此添加到您的ApplicationRecord
中以能够对任何模型进行重复数据删除:
class ApplicationRecord < ActiveRecord::Base
# …
def self.destroy_duplicates_by(*columns)
groups = select(columns).group(columns).having(Arel.star.count.gt(1))
groups.each do |duplicates|
records = where(duplicates.attributes.symbolize_keys.slice(*columns))
records.offset(1).destroy_all
end
end
end
然后可以调用destroy_duplicates_by
销毁所有具有给定列值的记录(第一条记录除外)。例如:
Model.destroy_duplicates_by(:name, :year, :trim, :make_id)
答案 6 :(得分:-1)
您可以尝试此sql查询,删除所有重复记录,但删除最新记录
DELETE FROM users USING users user WHERE (users.name = user.name AND users.year = user.year AND users.trim = user.trim AND users.id < user.id);