🧘

【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_allSQL関数を使って組み立てることができます。

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