🧘
【Rails】update_all で全件同じ値に…を回避!
ステージング環境のデータの中身をサンプルデータに一気に書き換えたい時がありました。
たとえば 、update_all を使って全ユーザーの名前やメールアドレスを変えようとすると、次のようなコードを書きたくなるかもしれません。
User.update_all(name: "テストユーザー", email: "test@example.com")
もちろんこれでも更新はされますが、全員が同じ値になってしまうという落とし穴があります。それでもいいのですが、どうせなら全部異なる値にもしてみたい🧐
問題:全件同じ値になってしまう
update_all は SQL の UPDATE ... SET を直接実行するため、各レコードに対して個別の値を入れることができません。
UPDATE users SET name = 'テストユーザー';
このようなSQLが発行されるため、全てのユーザーが "テストユーザー" になります。
解決策
では、個々のレコードに応じて動的に値を設定したい場合、どうすればよいでしょうか?
いくつかの解決策を紹介します。
①:SQLのCONCATで動的に加工
たとえば、メールアドレスを user_#{id}@example.com のようにしたい場合は、update_all で SQL関数を使って組み立てることができます。
User.update_all("email = CONCAT('user_', id, '@example.com')")
これにより、各行ごとに id に応じた値が設定されます。
②:find_each + update_columns
ActiveRecordの find_each を使って処理を行う方法です。
User.find_each do |user|
user.update_columns(
name: "ユーザー#{user.id}",
email: "user_#{user.id}@example.com"
)
end
-
update_columnsはバリデーションを通さず直接更新 - 件数が多いとパフォーマンスは落ちる可能性あり
③: activerecord-importを使用
activerecord-import を使えば、レコードをまとめて更新するような処理も効率よく書けます。
users = User.all.to_a
users.each do |user|
user.name = "ユーザー#{user.id}"
user.email = "user_#{user.id}@example.com"
end
User.import users, on_duplicate_key_update: [:name, :email]
- 1クエリでまとめて更新されるため高速
- 使い方などはこちら
比較
| 方法 | 個別の値可否 | パフォーマンス | 備考 |
|---|---|---|---|
update_all(固定値) |
❌ | ◎ | 全件同じ値になる |
update_all + SQL関数 |
◎ | ◎ | SQL文に依存 |
find_each + update_columns
|
◎ | △ | 件数が多いと遅い |
activerecord-import |
◎ | ◎ | gemが必要、※DB依存 |
※ 使用しているDB(MySQL/PostgreSQLなど)によって on_duplicate_key_update の構文が異なるため、実際に使う前に確認しておく必要があります。
まとめ
一括更新は目的や件数、DBの種類によって適した方法が変わります。
大量の更新処理では、速度と柔軟性のバランスを考えて手法を選びましょう🚀
Discussion