Program Tip

여러 열을 기반으로 중복 레코드를 제거 하시겠습니까?

programtip 2020. 10. 25. 12:49
반응형

여러 열을 기반으로 중복 레코드를 제거 하시겠습니까?


저는 Heroku를 사용하여 Ruby on Rails 애플리케이션을 호스팅하고 있으며 어떤 이유로 든 중복 된 행이있을 수 있습니다.

2 개 이상의 기준에 따라 중복 레코드를 삭제하지만 해당 중복 컬렉션의 레코드는 1 개만 유지하는 방법이 있습니까?

제 사용 사례에서는 데이터베이스에 자동차에 대한 제조사 및 모델 관계가 있습니다.

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

이름, 연도 및 트림이 동일하지만 해당 레코드 중 하나를 유지하는 모든 모델 레코드를 삭제하고 싶습니다 (즉, 레코드가 필요하지만 한 번만 필요함). 일부 활성 레코드 쿼리를 쉽게 실행할 수 있도록 Heroku 콘솔을 사용하고 있습니다.

어떤 제안?


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
  • 모두 찾기
  • 고유성을 위해 필요한 키로 그룹화
  • 그룹화 된 모델의 해시 값에 대한 루프
  • 하나의 사본을 유지하기 위해 첫 번째 값을 제거하십시오
  • 나머지는 삭제

사용자 테이블 데이터가 아래와 같은 경우

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

** 레일 4 **

ActiveRecord 4는 .not2 단계에서 다음을 작성할 수 있는 방법을 소개합니다 .

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

@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' 

다음을 시도해 볼 수 있습니다. (이전 답변을 기반으로)

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

모든 유효한 기록을 얻으려면. 그리고:

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

to remove the unneeded records. And certainly, you can make a migration that adds a unique index for the three columns so this is enforced at the DB level:

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

To run it on a migration I ended up doing like the following (based on the answer above by @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

You can try this sql query, to remove all duplicate records but latest one

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);

참고URL : https://stackoverflow.com/questions/14124212/remove-duplicate-records-based-on-multiple-columns

반응형