LaravelのupdateOrCreateの使い方|firstOrCreate・upsertとの違いも解説

基本文法・構文ガイド

LaravelのupdateOrCreateは、指定した条件のレコードが存在すれば更新し、存在しなければ新規作成する便利なEloquentメソッドです。この記事では、実際にコードを実行して確認した動作をもとに、基本的な使い方からfirstOrCreateupsert()との違い、リレーション経由での使用例、注意すべきポイントまで解説します。

updateOrCreateの早見表

メソッド レコードが無い場合 レコードが有る場合 対象件数 戻り値
updateOrCreate() 作成する 更新する 1件 モデルインスタンス
firstOrCreate() 作成する そのまま返す(更新しない) 1件 モデルインスタンス
firstOrNew() 未保存のインスタンスを生成 そのまま返す 1件 モデルインスタンス(未保存の場合あり)
upsert() 作成する 更新する 複数件を1クエリで一括処理 影響行数(int)

updateOrCreateの基本的な使い方

updateOrCreate()は第1引数に「検索条件」、第2引数に「作成・更新する値」を配列で渡します。検索条件に一致するレコードがあれば第2引数の内容で更新し、無ければ検索条件と値をマージして新規作成します。

$user = User::updateOrCreate(
    ['email' => 'user@example.com'],
    ['name' => 'John Doe', 'age' => 30]
);

実際にローカル環境(Laravel 13.12.0、SQLite)で動作を確認したところ、次のようになりました。

// 1回目: レコードが無いので新規作成される
$u1 = User::updateOrCreate(['email' => 'a@example.com'], ['name' => 'Alice', 'age' => 20]);
$u1->wasRecentlyCreated; // true
User::count(); // 1

// 2回目: 同じemailで呼ぶと、既存レコードが更新される(新規作成されない)
$u2 = User::updateOrCreate(['email' => 'a@example.com'], ['name' => 'Alice Updated', 'age' => 21]);
$u2->wasRecentlyCreated; // false
User::count(); // 1(変わらない)
$u1->id === $u2->id; // true(同一レコード)
$u2->name; // "Alice Updated"

ポイントは、第1引数(検索条件)で指定したカラムは新規作成時にそのまま保存され、第2引数(値)で指定したカラムは新規作成・更新のどちらでも上書きされるという点です。両方に同じキーを含めても問題ありません。

戻り値とwasRecentlyCreatedで作成/更新を判定する

updateOrCreate()の戻り値は常にモデルインスタンスです。作成されたか更新されたかを処理内で分岐したい場合は、モデルのwasRecentlyCreatedプロパティを確認します。

$product = Product::updateOrCreate(
    ['sku' => $sku],
    ['price' => $price, 'stock' => $stock]
);

if ($product->wasRecentlyCreated) {
    Log::info("新規商品を登録しました: {$sku}");
} else {
    Log::info("既存商品を更新しました: {$sku}");
}

リレーション経由でupdateOrCreateを使う

updateOrCreate()はリレーションのクエリビルダからも呼び出せます。この場合、外部キーは自動的に検索条件・作成データへ含まれるため、明示的に指定する必要がありません。実際にhasManyリレーションで検証すると、次のようにauthor_idが自動でセットされました。

class Author extends Model
{
    public function books()
    {
        return $this->hasMany(Book::class);
    }
}

$author = Author::find(1);

// author_id は指定しなくても自動的に $author->id がセットされる
$book = $author->books()->updateOrCreate(
    ['isbn' => '978-4-00-000000-0'],
    ['title' => 'こころ', 'stock' => 5]
);

$book->author_id === $author->id; // true

注意点として、検索条件に指定していないカラム(上記の例ではtitle)は一致判定に使われません。同じisbntitleを変えて呼び出すと、タイトルは新しい値で上書きされるだけで別レコードにはなりません。逆に、検索条件に含めたカラムの値を変えると別レコードとして新規作成されるため、「何を条件にレコードを同一視するか」を最初に設計しておくことが重要です。

updateOrCreateとfirstOrCreate・firstOrNewの違い

updateOrCreate()と混同されやすいメソッドにfirstOrCreate()firstOrNew()があります。いずれも第1引数に検索条件を渡す点は共通していますが、レコードが存在した場合の挙動が異なります。

  • firstOrCreate():レコードがあればそのまま返す。第2引数の値で更新はしない
  • firstOrNew():レコードが無い場合、保存されていないインスタンスを返す(呼び出し側でsave()が必要)
  • updateOrCreate():レコードがあれば第2引数の値で更新して保存する

「既存データがあれば内容を最新化したい」場合はupdateOrCreate()、「無ければ作るだけで、既存データには触れたくない」場合はfirstOrCreate()を選びます。

大量データを扱う場合はupsert()も検討する

updateOrCreate()は1回の呼び出しにつきSELECTとINSERT/UPDATEが発生するため、ループの中で大量に呼び出すとレコード数だけクエリが発生し、パフォーマンスの問題になりやすい方法です。複数件をまとめて登録・更新したい場合は、クエリビルダのupsert()を使うと1回のクエリで処理できます。

// 複数件をまとめてupsert(第2引数: 一意判定に使うカラム、第3引数: 重複時に更新するカラム)
Item::upsert(
    [
        ['sku' => 'A1', 'stock' => 10],
        ['sku' => 'A2', 'stock' => 5],
    ],
    ['sku'],
    ['stock']
);

実際に検証したところ、upsert()は指定した更新カラムにupdated_atを含めなくても、タイムスタンプ付きモデルであれば自動的にupdated_atが更新されました。ただしupsert()はEloquentのcreating/updatingなどのモデルイベントを発火しない点に注意してください。イベントを使った処理(通知やログなど)が必要な場合は、件数が少なければupdateOrCreate()、多い場合はイベント処理を別途用意した上でのupsert()検討が現実的です。

updateOrCreateを使う際の注意点

検索条件は完全一致で判定される

検索条件に指定した配列はwhere()と同様に扱われ、部分一致や曖昧な判定はできません。「同一レコードとみなす条件」を過不足なく指定することが大切です。

一意制約(unique制約)を設定しておく

Laravelの内部実装を確認すると、updateOrCreate()firstOrCreate()を経由し、レコード作成時に一意制約違反(UniqueConstraintViolationException)が発生した場合は自動的に該当レコードを再取得する仕組みになっています。つまり、検索条件に使うカラムにデータベース側のunique制約が設定されていれば、同時アクセスによる重複作成もある程度安全に扱われます。逆に言えば、アプリケーション側の条件指定だけに頼り、DB側にunique制約が無い場合は、同時実行時に重複レコードが作成される可能性が残るため、マイグレーションで$table->unique()を設定しておくことをおすすめします。

よくある質問(FAQ)

updateOrCreateとは?

指定した条件に一致するレコードがあれば値を更新し、無ければ条件と値をマージして新規作成するEloquentのメソッドです。「レコードの有無を確認してから作成/更新を出し分ける」処理を1行で書けます。

updateOrCreateとfirstOrCreateの使い分けは?

既存データの内容を最新化したいならupdateOrCreate()、既存データがあればそれを変更せずそのまま使いたいならfirstOrCreate()を使います。

updateOrCreateで大量データを処理すると遅くなりますか?

1件ごとにSELECTとINSERT/UPDATEが発生するため、件数が多いループ処理では遅くなりがちです。大量データの一括登録・更新にはupsert()の利用を検討してください。

updateOrCreateはトランザクション内で使えますか?

使えます。他の更新処理と合わせてDB::transaction()で包むことで、一連の操作を原子的に行えます。

まとめ

LaravelのupdateOrCreate()は、レコードの有無を判定してから作成・更新を出し分ける処理を簡潔に書けるメソッドです。実際に検証した結果をもとに整理すると、次の点を押さえておくと安全に使えます。

  • 戻り値は常にモデルインスタンスで、wasRecentlyCreatedで作成/更新を判定できる
  • リレーション経由で呼び出すと外部キーが自動セットされる
  • 既存データを変更したくない場合はfirstOrCreate()を使う
  • 大量データの一括処理にはupsert()を検討する
  • 検索条件のカラムには一意制約を設定し、重複作成を防ぐ

データ更新の基本パターンについては「Laravel Eloquentでデータ更新を効果的に行う方法とベストプラクティス」、hasManyリレーションの基本は「LaravelのHasManyリレーションを使いこなす方法とベストプラクティスガイド」もあわせて参考にしてください。Eloquentの基本操作を一から確認したい方は「Laravel Eloquentとは?使い方の基本からリレーション・クエリビルダとの違いまで徹底解説」もご覧ください。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント