NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #2453 most downloaded on Packagist
Insert and update batch (bulk) in laravel
Last release 3 days ago
05 Oct 2026
Release timing varies
gaps range from 2 weeks to 1.3 years
Some releases are documented
notes for 14 of 36 stable releases
Nothing withdrawn
no release was ever pulled
9 years old
36 releases · first in 2018
Counter and conditional upserts, rows back from upserts, a report of skipped duplicates, fast imports, row validation and exports. Everything is addit
Counter and conditional upserts, rows back from upserts, a report of skipped duplicates, fast
imports, row validation and exports. Everything is additive, so upgrading from 3.2 needs no code
changes:
composer update mavinoo/laravel-batch
Batch::upsert(new Stock, $rows, ['sku'], ['qty' => ['+']]); // qty = stored qty + new qty['-'], ['max'] and ['min'] work the same way, on every database.Batch::upsert(new Product, $rows, ['sku'], null, ['onlyIf' => ['updated_at' => '>']]);$users = Batch::upsertReturning(new User, $rows, ['email'], ['name'], ['id', 'email']);
$result = Batch::insertOrIgnoreRows(new User, $rows, ['email']);
// ['inserted' => 98, 'skipped' => [['email' => 'dup@example.com', ...], ...]]insertOrIgnoreRows() was checked with 8 concurrent writers inserting overlapping keys onBatch::import(new Order, storage_path('orders.csv'), ['fast' => true]);COPY on PostgreSQL and LOAD DATA LOCAL INFILE on MySQL and MariaDB.| 1,000,000-row CSV | import | fast |
|---|---|---|
| PostgreSQL | 18.7 s | 6.6 s |
| MySQL 8.4 | 18.7 s | 8.4 s |
| MariaDB | 12.6 s | 6.4 s |
Batch::import(new User, 'users.csv', ['rules' => ['email' => 'required|email']]);
// ['totalRows' => 9988, 'skipped' => 12, 'errors' => [15 => ['email' => [...]]]]User::where('active', true)->exportTo(storage_path('users.csv'), ['maxRows' => 100000]);maxRows splits the output into numbered parts.User::withoutTimestamps(fn () => Batch::update(new User, $rows)) now has documentation and tests
for every method (#97).
upsert() takes an optional fifth $options argument.import() also returns an errors key.Full changelog: https://github.com/mavinoo/laravelBatch/blob/master/CHANGELOG.md
Pull request: #128
One column per quarter.
upsert($table, $rows, ['sku'], ['qty' => ['+']]) adds to the stored value.
Also ['-'], ['max'] and ['min'], on every database.onlyIf option, e.g. ['onlyIf' => ['updated_at' => '>']], updates an
existing row only when the new value wins.upsertReturning(): upsert and get every row of the batch back as stored, in input order.insertOrIgnoreRows(): insert, skip duplicates, and report exactly which rows were skipped.Model::withoutTimestamps() turns timestamps off for every method (#97).import() option fast: load with COPY on PostgreSQL and LOAD DATA LOCAL INFILE on MySQL and
MariaDB, 2 to 2.8 times faster. Bad values that MySQL would store with a warning throw instead.import() option rules: validate each row with Laravel's validator; failing rows are skipped and
their messages returned in the new errors result, by row number.export() and the exportTo() builder macro: write the rows a query matches to CSV, TSV or JSON
Lines files (also gzipped), a chunk at a time, optionally split into parts.upsert() takes an optional fifth $options argument.import() also returns an errors key.New bulk operations for large tables and files, JSON column updates, and two JSON bug fixes. Everything is additive, so upgrading from 3.1 needs no co
New bulk operations for large tables and files, JSON column updates, and two JSON bug fixes.
Everything is additive, so upgrading from 3.1 needs no code changes:
composer update mavinoo/laravel-batch
Batch::update(new User, [
['id' => 1, 'settings->theme' => 'dark', 'settings->notify->email' => false],
]);updated_at only changes when the document changes.array, json, collection,AsCollection, ...) are encoded the way the model stores them, in update, insert and upsert.insertGetIds(): insert rows and get their ids (#48)$ids = Batch::insertGetIds(new User, $rows); // [101, 102, ...] in row orderimport(): import files and iterables with constant memoryBatch::import(new User, storage_path('users.csv'), ['mode' => 'upsert', 'uniqueBy' => ['email']]);.gz), open streams, or any iterable such asModel::cursor().| 500,000-row CSV | import() |
array + insertRows() |
|---|---|---|
| Memory | 7 MB | 765 MB |
| Time | 30–45% faster |
splitFile(): split large filesBatch::splitFile($path, lines: 50000); // users-001.csv, users-002.csv, ...deleteInChunks(), updateInChunks(), archive()Log::where('created_at', '<', now()->subYear())->deleteInChunks(10000);
User::where('active', false)->updateInChunks(['status' => 'archived']);
Order::where('created_at', '<', '2020-01-01')->archiveTo('orders_archive');DELETE.archive() copies and deletes each chunk in one transaction.sync(): make a table match a listProduct::where('supplier_id', 5)->syncRows($feedRows, ['sku']);
// ['inserted' => 120, 'updated' => 4800, 'deleted' => 35]"ABC" and "abc" are the same key.json column failed with operator does not exist: json <> unknown.json columns touched updated_at even when their value didn't change.Full changelog: https://github.com/mavinoo/laravelBatch/blob/master/CHANGELOG.md
Pull request: #126
column->key (#64, #83, #111). Values keep their JSON type,
missing objects are created and updated_at only changes when the document changes.insertGetIds() and HasBatch::batchInsertGetIds(): insert rows and get their auto-increment ids,
in row order (#48).import() and HasBatch::batchImport(): import rows from CSV, TSV and JSON Lines files (also
gzipped), open streams or any iterable, a chunk at a time with constant memory. Supports insert,
insert-ignore and upsert, column mapping, transforms, progress and error callbacks.splitFile(): split a large file into parts by number of records and / or size, without cutting
a record or a multi-line CSV field in half.deleteInChunks(), updateInChunks() and archive(): delete, update or move the rows a query
matches a chunk at a time, walking the table in primary key order. Also available as
deleteInChunks(), updateInChunks() and archiveTo() on Eloquent and query builders.sync(): insert, update and delete the rows a query matches so they match a list, in one
transaction. The database compares the keys, so its collation decides which are equal. Also
available as syncRows() on Eloquent builders.json column failed with "operator does not exist: json <> unknown".json columns touched updated_at even when their value didn't change.New ways to write batches, a preview mode, and much faster large updates. Everything is additive, so upgrading from 3.0 needs no code changes:
New ways to write batches, a preview mode, and much faster large updates. Everything is
additive, so upgrading from 3.0 needs no code changes:
composer update mavinoo/laravel-batch
updateByKeys(): update rows matched on any number of key columns.insertRows(): insert rows given as column => value pairs.upsert(): insert or update in one query, with timestamps (created_at only on newdeleteByKeys(): delete rows matched on one or more key columns. Models usingSoftDeletes are soft deleted unless $force is true.pretend(): see the SQL and bindings a call would run, without running it orHasBatch methods for every operation: batchUpdateMultipleCondition(),batchUpdateWithTwoIndex(), batchUpdateByKeys(), batchInsertRows(),batchUpsert(), batchDeleteByKeys().Each UPDATE ... CASE statement now holds at most 100 rows. The database checks every
row against the CASE branches one by one, so large statements slowed down quickly.
| Updating 50,000 rows | 3.0.0 | 3.1.0 |
|---|---|---|
| MySQL 8.4 | 30.5 s | 3.1 s |
| MariaDB 10.11 | 25.3 s | 2.4 s |
| PostgreSQL 16 | over 15 min | 2.5 s |
| SQLite 3.45 | 3.7 s | 3.4 s |
(new Model)->updateMultipleCondition(); use the staticModel::batchUpdateMultipleCondition(). It will be removed in 4.0.InvalidArgumentExceptions for rows that aren't arrays, non-string columnFull changelog: https://github.com/mavinoo/laravelBatch/blob/v3.1.0/CHANGELOG.md
Pull request: #125
updateByKeys(): update rows matched on any number of key columns.insertRows(): insert rows given as column => value pairs.upsert(): insert or update in one query, with timestamps, enums and automatic splitting of
large batches in one transaction.pretend(): return the statements a call would run, with bindings, without running them or
connecting to the database.deleteByKeys(): delete rows matched on one or more key columns. Soft-deleting models are soft
deleted unless $force is true.HasBatch methods: batchUpdateMultipleCondition(), batchUpdateWithTwoIndex(),
batchUpdateByKeys(), batchInsertRows(), batchUpsert() and batchDeleteByKeys().composer analyse script.UPDATE ... CASE statement now holds at most 100 rows. The
database checks every row against the CASE branches one by one, so big statements got slow
quickly. Updating 50,000 rows went from 30.5 s to 3.1 s on MySQL 8.4, from 25.3 s to 2.4 s on
MariaDB 10.11, and from over 15 minutes to 2.5 s on PostgreSQL 16.Batch instance, so the facade, batch() and the
HasBatch trait see the same pretend() state.HasBatch::updateMultipleCondition(); use the static
batchUpdateMultipleCondition(). It will be removed in 4.0.A major release that rewrites how queries are built. Every 2.x user should upgrade : all 2.x versions build SQL by escaping values by hand, which allo
A major release that rewrites how queries are built. Every 2.x user should upgrade:
all 2.x versions build SQL by escaping values by hand, which allows SQL injection
(always on PostgreSQL, and through index values and JSON keys on every database).
3.0 sends every value as a bound parameter and lets Laravel's grammar quote every
column and table name.
composer require "mavinoo/laravel-batch:^3.0"
Requires PHP 8.1+ and Laravel 10 – 13. Upgrading from 2.x takes a few
minutes for most projects: see the upgrade guide.
$raw argument is removed. Wrap only the values that are SQL in DB::raw():['id' => 1, 'score' => DB::raw('score * 2')]. Passing true for the old argumentint and insert() returns array. Empty input returns0 / zero counts instead of false.InvalidArgumentException before anything is written.BatchInterface and Batch declare return types.Mavinoo\Batch\Common\Common and the global str() helper are removed.DB::raw() in values, index values and conditions.composer test) and GitHub Actions on PHP 8.1 – 8.5,updateMultipleCondition() with string keys, on PostgreSQL, with nullupdated_at only changes for rows that change, on every database (#120).CREATED_AT or UPDATED_AT set to null (#107).insert() rolls back on the model's own connection, and $insertIgnore worksFull changelog: https://github.com/mavinoo/laravelBatch/blob/v3.0.0/CHANGELOG.md
Pull request: #124
Upgrading from 2.x: see UPGRADE.md.
$raw argument; wrap raw SQL values in DB::raw() instead. Passing true throws.int, insert() returns array. Empty input returns 0 / zero counts
instead of false.InvalidArgumentException instead of returning false, failing with an SQL
error, or throwing ArgumentCountError / TypeError.BatchInterface and Batch.Mavinoo\Batch\Common\Common and the global str() helper.DB::raw() values in rows, index values and conditions.composer test) for SQLite, MySQL, MariaDB and PostgreSQL.updateMultipleCondition() with string key values, on PostgreSQL, with null conditions, and
when a condition column is updated in the same call on MySQL.updated_at is only touched for rows that change, on every database (#120).insert() uses a transaction on the model's connection, and $insertIgnore works on
PostgreSQL and SQLite.CREATED_AT or UPDATED_AT set to null (#107).Nullable parameters are now declared as ?string , which works on PHP 7.1 through 8.5 and still avoids the PHP 8.4 deprecation notice.
A bug-fix release for v2.5.0. All v2.5.0 users should upgrade, especially on PHP 7.
syntax error, unexpected '|'. Nullable?string, which works on PHP 7.1 through 8.5updated_at is updated again for rows that change, inupdate(), updateWithTwoIndex() and updateMultipleCondition().updated_at is now updated when a column changes from NULL to a value.updated_at passed for one row is kept when other rows in theupdateWithTwoIndex() writes values the same way as v2.4.1 again: leading'0912…'), false is stored as 0, and string values workupdateMultipleCondition() no longer fails withMethod Illuminate\Support\Stringable::toString does not exist on olderupdated_at is still only touched when a value actually changes (#120), and
now this works on every database.
No code changes are needed:
composer update mavinoo/laravel-batch
Tested on PHP 7.1 – 8.5, with Laravel 8 and 11, on MySQL/MariaDB, PostgreSQL and SQLite.
Full Changelog: v2.5.0...v2.5.1
updated_at not being updated on MySQL / MariaDB, when a column changes from NULL, and an
explicit updated_at being dropped (#123).updateWithTwoIndex() dropping leading zeros, failing on false and on PostgreSQL (#123).updateMultipleCondition() failing on older Laravel versions with Stringable::toString (#118).Fix PHP 8.4 deprecation notices: parameters that default to null are now declared as string|null in Batch , BatchInterface and HasBatch by @neoighodar…
⚠️ Superseded by v2.5.1, which fixes the known issues below and PHP 7 support. Please upgrade.
null are nowstring|null in Batch, BatchInterface and HasBatchupdated_at when a value actually changesupdateWithTwoIndex() no longer writes to its two index columns, and uses aThese were found by testing this release against v2.4.1 on MySQL/MariaDB,
PostgreSQL and SQLite:
update() and updateMultipleCondition() no longer updateupdated_at for rows that change. MySQL applies SET assignments left to right,NULL to a value, updated_atupdated_at passed for one row is dropped whenupdateWithTwoIndex():
'09121234567' is stored as 9121234567) on MySQL and SQLitefalse fails on MySQL and PostgreSQL, and is stored as an empty string on SQLitecolumn "..." does not exist)If you use updated_at on MySQL, store numeric strings or booleans with
updateWithTwoIndex(), or use updateWithTwoIndex() on PostgreSQL,
stay on v2.4.1 for now:
composer require mavinoo/laravel-batch:2.4.1
All of these are fixed in the upcoming v3.0.0.
Full Changelog: v2.4.1...v2.5.0
updated_at when a value changes (#119).updateWithTwoIndex() writing to its index columns (#122).fixed bugs! in method update Multiple Condition 146182143 .
fixed bugs! in method update Multiple Condition 146182143.
Determine database driver from model, not default config Changelog https://github.com/AdairHomes/laravelBatch/commit/8eeca0c169e72faab2c568f7cb9e2d93e
Determine database driver from model, not default config
Changelog https://github.com/AdairHomes/laravelBatch/commit/8eeca0c169e72faab2c568f7cb9e2d93e05710a4
Disable backtick when incrementing Changelog a561f23
Disable backtick when incrementing
Changelog a561f23
Full Changelog : v2.3.2...v2.3.4
Full Changelog: v2.3.2...v2.3.4
Insert and update batch (bulk) in laravel
Insert and update batch (bulk) in laravel
support to the case where there is no 0th array
As it stands now, this would be an error in this case.
bug fixed: commit
Insert and update batch (bulk) in laravel
Insert and update batch (bulk) in laravel
bug fixed.
Insert and update batch (bulk) in laravel
Insert and update batch (bulk) in laravel
more bugs fixed.
Insert and update batch (bulk) in laravel
Insert and update batch (bulk) in laravel
update method insert, disableBacktick for driver pgsql & sqlsrv
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →