0

update(), updateOrInsert(), increment(), decrement()

Intermediate5 min read·lv-13-012
sql

Concept

Update methods modify existing rows. Like inserts, Query Builder updates bypass model events and casts — plan accordingly.

update(array $data): UPDATE table SET ... WHERE .... Returns the number of affected rows. Requires a WHERE constraint in production — updating without WHERE affects all rows.

updateOrInsert(array $attributes, array $values = []): If a row matching $attributes exists, updates it with $values. If not, inserts merged data. Similar to Eloquent's updateOrCreate but returns a boolean, not a model.

increment(string $column, int|float $amount = 1, array $extra = []): Atomic UPDATE SET col = col + N. Safe for concurrent updates — avoids race conditions that would occur with read-modify-write in PHP. $extra allows updating other columns in the same query.

decrement(string $column, int|float $amount = 1, array $extra = []): Inverse.

update(DB::raw(...)): For complex update expressions like SET col = JSON_SET(col, '$.key', 'value').

Mass updates: User::where('active', false)->update(['deleted_at' => now()]) — never loads models. Fast but no model events.

updateOrInsert vs upsert: updateOrInsert works one row at a time (SELECT + INSERT or UPDATE). upsert is a single SQL statement supporting batch rows — prefer upsert for batch operations.

Code Example

php
<?php
use Illuminate\Support\Facades\DB;

// Basic update
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['name' => 'Alice Smith', 'updated_at' => now()]);
// Returns: int (number of rows changed)

// Mass update — no model events
DB::table('users')
    ->where('last_login_at', '<', now()->subMonths(6))
    ->update(['active' => false, 'updated_at' => now()]);

// updateOrInsert
DB::table('user_settings')->updateOrInsert(
    ['user_id' => 1, 'key' => 'notifications'], // search criteria
    ['value' => 'email', 'updated_at' => now()] // values to set (or update)
);

// increment / decrement — atomic, race-condition safe
DB::table('products')->where('id', 5)->increment('stock', 10);
DB::table('products')->where('id', 5)->decrement('stock');  // -1

// increment with extra columns in same query
DB::table('posts')
    ->where('id', $postId)
    ->increment('views', 1, ['last_viewed_at' => now()]);
// SQL: UPDATE posts SET views = views + 1, last_viewed_at = ? WHERE id = ?

// Eloquent increment — works the same, model events DON'T fire
User::where('active', true)->increment('login_count');

// Using DB::raw in update
DB::table('products')
    ->where('id', 1)
    ->update([
        'metadata' => DB::raw('JSON_SET(metadata, "$.last_checked", NOW())'),
        'updated_at' => now(),
    ]);

// Multiple increments in one query
DB::table('stats')
    ->where('date', today())
    ->update([
        'page_views'    => DB::raw('page_views + 1'),
        'unique_visits' => DB::raw("unique_visits + IF(is_new_visitor, 1, 0)"),
    ]);

// Safe pattern — always include WHERE
$rowsUpdated = DB::table('orders')
    ->where('status', 'pending')
    ->where('created_at', '<', now()->subDays(7))
    ->update(['status' => 'expired']);
if ($rowsUpdated === 0) {
    // No rows matched — no update happened
}