根据多列删除重复记录?

ser*_*erg 72 activerecord duplicates destroy ruby-on-rails-3

我正在使用Heroku来托管我的Ruby on Rails应用程序,出于某种原因,我可能会有一些重复的行.

有没有办法根据2个或更多标准删除重复记录,但只保留该重复集合的1个记录?

在我的用例中,我在我的数据库中有汽车的Make和Model关系.

Make      Model
---       ---
Name      Name
          Year
          Trim
          MakeId
Run Code Online (Sandbox Code Playgroud)

我想删除所有具有相同名称,年份和修剪的模型记录,但保留其中一条记录(意思是,我需要记录但只有一次).我正在使用Heroku控制台,因此我可以轻松地运行一些活动的记录查询.

有什么建议?

Adi*_*ghi 133

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
Run Code Online (Sandbox Code Playgroud)
  • 找到所有
  • 将它们分组到您需要唯一性的键上
  • 循环分组模型的哈希值
  • 删除第一个值,因为您要保留一个副本
  • 删除其余的

  • 对大型数据集工作但效率极低.更快的方法是首先使用此算法收集数组中的ID,然后使用一个DELETE FROM sql语句删除id数组. (5认同)
  • 有趣的方法,但是有大量记录却效率低下。想知道是否有一种方法可以主动记录自己。 (2认同)

小智 50

如果你的用户表数据如下

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 > 
Run Code Online (Sandbox Code Playgroud)

电子邮件ID是重复的,因此我们的目标是从用户表中删除所有重复的电子邮件ID.

步骤1:

获取所有不同的电子邮件记录ID.

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

第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
Run Code Online (Sandbox Code Playgroud)

**铁路4**

ActiveRecord 4介绍了.not允许您在步骤2中编写以下内容的方法:

User.where.not(id: ids).destroy_all
Run Code Online (Sandbox Code Playgroud)

  • 这是危险的:当你没有重复项时再次运行它会删除比你想要的更多的内容,因为逻辑是“删除除 D 之外的所有内容”。我认为更好的逻辑是“删除 D 中的所有内容”,其中 D 是重复行的 id 列表。 (3认同)

mac*_*atz 12

类似@Aditya Sanghi的的答案,但这种方式会更好的性能,因为你只选择重复,而不是每加载模型对象到内存中,然后遍历所有的人.

# 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
Run Code Online (Sandbox Code Playgroud)

此外,如果您真的不希望在此表中重复数据,您可能希望向表中添加多列唯一索引,类似于以下行:

add_index :models, [:name, :year, :trim], unique: true, name: 'index_unique_models' 
Run Code Online (Sandbox Code Playgroud)


小智 7

您可以尝试以下操作:(基于先前的答案)

ids = Model.group('name, year, trim').pluck('MIN(id)')
Run Code Online (Sandbox Code Playgroud)

获取所有有效记录。然后:

Model.where.not(id: ids).destroy_all
Run Code Online (Sandbox Code Playgroud)

删除不需要的记录。当然,您可以进行迁移,从而为三列添加唯一索引,以便在数据库级别实施该迁移:

add_index :models, [:name, :year, :trim], unique: true
Run Code Online (Sandbox Code Playgroud)