80

私はHerokuを使用してRubyonRailsアプリケーションをホストしていますが、何らかの理由で行が重複している可能性があります。

2つ以上の基準に基づいて重複レコードを削除するが、その重複コレクションのレコードを1つだけ保持する方法はありますか?

私のユースケースでは、データベースに車のメーカーとモデルの関係があります。

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

名前、年、トリムが同じであるが、それらのレコードの1つを保持しているすべてのモデルレコードを削除したい(つまり、レコードが必要なのは1回だけです)。Herokuコンソールを使用しているので、アクティブレコードクエリを簡単に実行できます。

助言がありますか?

4

7 に答える 7

154
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つのコピーを保持するため、最初の値を削除します
  • 残りを削除する
于 2013-01-02T15:15:47.277 に答える
53

以下のようなユーザーテーブルデータの場合

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
于 2013-08-06T09:11:33.643 に答える
17

@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' 
于 2016-01-12T07:41:56.993 に答える
11

次のことを試すことができます:(以前の回答に基づく)

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

すべての有効なレコードを取得します。その後:

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

不要なレコードを削除します。そして確かに、3つの列に一意のインデックスを追加する移行を行うことができるため、これはDBレベルで実施されます。

add_index :models, [:name, :year, :trim], unique: true
于 2018-04-06T17:33:51.450 に答える
4

移行でそれを実行するために、私は次のようにすることになりました(@ 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
于 2016-03-22T14:25:23.123 に答える
0

@ aditya-sanghiの回答に基づいており、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)
于 2020-05-20T11:34:23.083 に答える
-3

この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);
于 2015-06-01T07:41:01.597 に答える