DEV3 Technologies home

DEV3 Developer Forum

updateOrCreate vs upsert in Laravel: which one should I use?

Laravel helpSolved0 views
dev3 ·

I import 5,000 product rows from a CSV nightly. updateOrCreate in a loop is slow. Someone told me to use upsert, but what are the requirements and the differences in Laravel 12?

#laravel#eloquent#database

0

1 reply

dev3 · Accepted answer

They solve similar problems differently.

updateOrCreate

Product::updateOrCreate(['sku' => $row['sku']], ['name' => $row['name'], 'price' => $row['price']]);

One select then an insert or update per call. It fires model events, applies casts and mutators, and handles timestamps. It is not atomic, so two concurrent runs can both insert unless a unique index catches it. Fine for tens of rows.

upsert

Product::upsert(
    $rows,                      // array of arrays
    ['sku'],                    // unique columns
    ['name', 'price']           // columns to update on conflict
);

A single INSERT ... ON DUPLICATE KEY UPDATE (MySQL) or ON CONFLICT (Postgres/SQLite) statement per batch. Much faster, and atomic per statement.

Requirements and caveats

  • The "unique by" columns need a primary or unique index in the database on every driver except SQL Server. MariaDB and MySQL ignore the argument and use the table's primary and unique indexes, while SQLite and Postgres need the columns to match a unique index exactly.
  • No model events, no casts, no accessors: you must pass database-ready values (JSON encoded, enum values, dates as strings). Timestamps: Model::upsert() fills created_at/updated_at for you on models that use timestamps (the query builder's DB::table()->upsert() does not).
  • Chunk large imports to avoid parameter limits: collect($rows)->chunk(1000)->each(fn ($c) => Product::upsert($c->all(), ['sku'], ['name','price'])).
  • Rows are not matched by where conditions, only by index.

Other helpers

  • firstOrCreate: create only if absent, never updates.
  • insertOrIgnore: skip duplicates silently.
  • Product::query()->where(...)->update([...]) for bulk updates by condition.

For import jobs also wrap the loop in a queue job and log invalid rows rather than failing the whole batch.

0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.