Удалить дубликаты записей на основе нескольких столбцов?

Я использую Heroku для размещения моего приложения Ruby on Rails, и по той или иной причине у меня могут быть несколько повторяющихся строк.

Есть ли способ удалить повторяющиеся записи на основе двух или более критериев, но сохранить только одну запись этой дублированной коллекции?

В моем случае использования у меня есть отношения Make и Model для автомобилей в моей базе данных.

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

Я хотел бы удалить все записи модели, которые имеют одинаковое имя, год и обрезку, но сохраняют одну из этих записей (что означает, мне нужна запись, но только один раз). Я использую консоль Heroku, поэтому я могу легко запускать активные запросы к записи.

Любые предложения?

Ответ 1

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
  • Найти все
  • Группируйте их по клавишам, которые вам нужны для уникальности
  • Петля по сгруппированным значениям модели хэша
  • удалить первое значение, потому что вы хотите сохранить одну копию
  • удалить остальные

Ответ 2

Если ваши данные таблицы пользователя, как показано ниже

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 > 

Идентификатор электронной почты дублируется, поэтому наша цель - удалить все повторяющиеся идентификаторы электронной почты из таблицы пользователей.

Шаг 1:

Чтобы получить все отдельные идентификаторы почтовых ящиков.

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

Шаг 2:

Чтобы удалить дублированный идентификатор из таблицы пользователя с разными идентификаторами записей электронной почты.

Теперь массив ids содержит следующие идентификаторы.

[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

Ответ 3

Подобно ответу @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

Кроме того, если вы действительно не хотите дублировать данные в этой таблице, вы, вероятно, захотите добавить уникальный столбец с несколькими столбцами в таблицу, что-то вроде строк:

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

Ответ 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

Ответ 5

Вы можете попробовать этот 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);