🐙

upsert_allについて

に公開

Railsのupsert_allは、データベースに複数のレコードを効率的に挿入(insert)または更新(update)するためのメソッドです。大量のデータを一括で処理する際に、パフォーマンスを劇的に向上させることができます。

upsert_allの概要

upsert_allは、insertとupdateを組み合わせた造語です。

  • レコードが存在しない場合: 新しいレコードとして挿入(INSERT)されます。
  • レコードがすでに存在する場合: 既存のレコードの属性が更新(UPDATE)されます。

この処理を、単一のSQLクエリで実行するのがupsert_allの最大の特徴です。


upsert_allが便利な理由

1. 圧倒的なパフォーマンス

従来のfind_or_create_byや、レコードを一つずつcreateやupdateする処理は、レコードの数だけデータベースへのアクセスが発生します(N+1問題)。

upsert_allは、複数のレコードを一括で処理するため、データベースへのアクセス回数を最小限に抑えることができます。これにより、数千、数万といった大量のデータを扱う際に、処理時間を大幅に短縮できます。

2. アトミックな操作

upsert_allは単一のトランザクションとして実行されるため、途中でエラーが発生しても、すべての処理がロールバックされます。これにより、データの整合性を保ちやすくなります。


upsert_allの基本的な使い方

upsert_allメソッドは、ハッシュの配列を引数として受け取ります。

# usersテーブルにデータを一括で挿入・更新
User.upsert_all([
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com' }
])

この例では、id: 1とid: 2のレコードがすでに存在すれば更新され、id: 3のレコードが存在しなければ新規挿入されます。

注意点:インデックスの追加は必須

upsert_allで指定するユニークキー(デフォルトはid、またはunique_byで指定したカラム)には、データベースにユニークインデックスを必ず追加してください。

インデックスがない場合、データベースは行の存在を調べるためにテーブル全体をスキャンする必要があり、upsert_allのパフォーマンスメリットが失われてしまいます。

# db/migrate/xxxx_add_unique_index_to_users_email.rb
class AddUniqueIndexToUsersEmail < ActiveRecord::Migration[7.1]
  def change
    add_index :users, :email, unique: true
  end
end

注意点とデメリット

1. バリデーションとコールバックが実行されない

upsert_allは、Active Recordのインスタンスを生成せずに直接データベースを操作するため、validatesなどのバリデーションや、before_saveなどのコールバックは実行されません。

したがって、データの整合性をモデルに依存している場合は、upsert_allの利用には注意が必要です。

2. id以外のユニークキーを指定する必要がある場合

デフォルトではidをユニークキーとして扱いますが、unique_byオプションを使うことで、他のカラムをユニークキーとして指定できます。

# emailをユニークキーとして、既存のレコードを更新
User.upsert_all([
  { name: 'David', email: 'david@example.com' }
], unique_by: :email)

この例では、emailがdavid@example.comのレコードがすでに存在すれば更新され、なければ新規挿入されます。

まとめ

特徴 upsert_all createやupdate
パフォーマンス 高速(単一SQL) 低速(レコード数分のSQL)
バリデーション 実行されない 実行される
コールバック 実行されない 実行される
使用シーン 大量のデータの一括処理 単一レコードの操作、整合性重視

upsert_allは、大量データの初期投入や定期的な同期処理など、パフォーマンスを最優先したい場面で非常に強力な武器となります。ただし、データの整合性をモデルの機能に頼っている場合は、createやupdateを使い分ける必要があります。

Discussion