0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ruby on Railsで孤立したレコードを見つけて削除する方法

Posted at

# Ruby on Railsで孤立したレコードを見つけて削除する方法

以下の三つのテープルを定義してみます。

# app/models/notification.rb
class Notification < ActiveRecord::Base
  has_many :user_notifications
  has_many :users, through: :user_notifications
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :user_notifications
  has_many :notifications, through: :user_notifications
end

# app/models/user_notifications.rb
class UserNotification < ActiveRecord::Base
  belongs_to :user
  belongs_to :notification
end

残念ながら、has_many:の通知にdependent::destroyを追加するのを忘れた。 ユーザーまたは通知が削除されたときに、孤立したUserNotificationが残っていました。
この問題はdependent::destroyによって修正されましたが、それでもたくさんの孤立したレコードが残っていました。
孤立したレコードを削除する方法は3つあります。

Approach #1 — Bad

UserNotification.find_each do |user_notification|
  if user_notification.notification.nil? || user_notification.user.nil?
    user_notification.destroy
  end
end

これは各レコードに対して別々のSQLクエリを実行し、それが孤立しているかどうかをチェックし、そうであれば破棄します。

Approach #2 — Better, but still pretty bad

UserNotification.all.each do |user_notification|
  if user_notification.notification.nil? || user_notification.user.nil?
    user_notification.destroy
  end
end

これは、すべてのレコードをメモリにロードしてから、上記と同じチェックを実行しながらそれらを繰り返します。

Approach #3 — Good

UserNotification.where([
  "user_id NOT IN (?) OR notification_id NOT IN (?)",
  User.pluck("id"),
  Notification.pluck("id")
]).destroy_all

このアプローチでは、最初にすべてのユーザーと通知のIDを取得し、次に1つのクエリを実行してユーザーまたはクエリのいずれにも属していないすべてのユーザー通知を見つけます。

Benchmarks

それぞれの場合にかかる時間を見てみましょう。

          user       system     total    real
bad       3.020000   0.160000   3.180000 (  4.058246)
better    2.950000   0.170000   3.120000 (  3.982329)
good      0.010000   0.000000   0.010000 (  0.030346)

ユーザー通知の数が減っても、UserNotification.all.eachはUserNotifications.find_eachよりも高速です。これは、SQLクエリの実行回数が少ないためです。

Approach #4

  • データが多い場合、pluckよりselectのほうが高速です
UserNotification.where([
  "user_id NOT IN (?) OR notification_id NOT IN (?)",
  User.select("id"),
  Notification.select("id")
]).destroy_all
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?