3.

Laravel UPDATE クエリビルダ完全ガイド

編集
この記事の要点
  • 基本構文: DB::table("users")->where("id", 1)->update(["name" => "taro"])
  • increment / decrement で数値を 1 クエリで加減算
  • Eloquent では $user->save()Model::where()->update() の挙動の違いに注意 (イベント / タイムスタンプ)
  • updateOrCreate / updateOrInsert で「あれば UPDATE、無ければ INSERT」
  • WHERE を忘れると全行 UPDATE。本番事故の典型。
  • 楽観ロック (version カラム) で同時編集事故を防ぐ

クエリビルダによる UPDATE の基本

use Illuminate\Support\Facades\DB;

// 1 行更新
DB::table('users')
    ->where('id', 1)
    ->update([
        'name' => 'taro',
        'updated_at' => now(),
    ]);

// 戻り値は影響を受けた行数 (int)
$affected = DB::table('users')->where('status', 'pending')->update(['status' => 'active']);

内部的には UPDATE users SET name = ?, updated_at = ? WHERE id = ? という prepared statement が発行されます。

WHERE 必須 — 忘れると全行更新

クエリビルダの update() は WHERE を強制しません。意図せず UPDATE users SET name = 'taro' (全行) を投げてしまう事故が後を絶ちません。

// ❌ 全ユーザーが taro になる
DB::table('users')->update(['name' => 'taro']);

// ✅ 必ず where を付ける
DB::table('users')->where('id', 1)->update(['name' => 'taro']);

// 防御策: MySQL の sql_safe_updates を有効化
// SET sql_safe_updates = 1;
// → WHERE 句なしの UPDATE / DELETE は実行エラーになる

increment / decrement

// SET view_count = view_count + 1
DB::table('articles')->where('id', 10)->increment('view_count');

// 任意の量
DB::table('products')->where('id', 5)->increment('stock', 10);

// 同時に他カラムも更新
DB::table('users')->where('id', 1)->increment('login_count', 1, [
    'last_login_at' => now(),
]);

// decrement も同様
DB::table('products')->where('id', 5)->decrement('stock', 1);

$count = $row->count + 1; として再代入するよりレース条件に強いです (1 クエリで atomic)。

updateOrInsert — UPSERT のシンプル版

// 検索条件にマッチする行があれば UPDATE、無ければ INSERT
DB::table('user_settings')->updateOrInsert(
    ['user_id' => 1, 'key' => 'theme'],
    ['value'   => 'dark', 'updated_at' => now()]
);

Eloquent での UPDATE

Eloquent には 2 系統あります。save() 系 (モデルインスタンスを取得して属性変更 → 保存) と、クラスメソッド系 (where -> update を直接) です。

class User extends Model
{
    protected $fillable = ['name', 'email'];
}

// (A) 取得 → 属性変更 → save: モデルイベント (updating/updated) 発火、タイムスタンプ更新
$user = User::find(1);
$user->name = 'taro';
$user->save();

// (B) 一括 fill + save
$user->fill(['name' => 'taro', 'email' => 'taro@example.com'])->save();

// (C) クラスメソッド: イベント不発火、タイムスタンプ非更新 (一般的に高速)
User::where('status', 'pending')->update(['status' => 'active']);

// (D) updateOrCreate: あれば UPDATE、無ければ INSERT
User::updateOrCreate(
    ['email' => 'taro@example.com'],   // 検索
    ['name'  => 'taro']                // 更新 or 作成値
);

(A) と (C) の違い

項目$user->save()Model::where()->update()
モデルイベントupdating/updated 発火発火しない
updated_at自動更新更新しない (明示が必要)
SELECT事前に 1 回必要不要 (UPDATE 一発)
Mutator / Caster適用される適用されない
パフォーマンス遅い速い

JOIN を伴う UPDATE

// users.points をその user の orders 合計で更新
DB::table('users')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->where('orders.status', 'paid')
    ->update(['users.points' => DB::raw('users.points + orders.amount * 0.01')]);

// MySQL: UPDATE users INNER JOIN orders ON ... SET ...
// PostgreSQL: UPDATE users SET ... FROM orders WHERE ... (構文差注意)

Mass Assignment 保護

class User extends Model
{
    protected $fillable = ['name', 'email'];   // ホワイトリスト
    // または
    protected $guarded = ['id', 'is_admin'];   // ブラックリスト
}

// is_admin は $fillable にないので無視される
User::create($request->all());

// 明示的に値を変えたいときは直接代入で
$user->is_admin = true;
$user->save();

楽観ロック (Optimistic Lock)

2 人が同時に同じ行を編集して片方の変更が消える事故を防ぎます。version カラムを用意し、UPDATE 時に WHERE 句で照合します。

$post = Post::find($id);
$currentVersion = $post->version;

// ユーザーが入力フォームを操作している間に別の人が更新したかも...
$affected = Post::where('id', $id)
    ->where('version', $currentVersion)
    ->update([
        'title'   => $request->title,
        'version' => $currentVersion + 1,
    ]);

if ($affected === 0) {
    throw new RuntimeException('他の人が更新しました。再読み込みしてください。');
}

FAQ

Q: update() の戻り値が 0 になる
A: WHERE にマッチした行が無いか、値が全く変わっていない (MySQL は変更無しを 0 とカウント) ケース。SELECT で存在確認してから判断してください。

Q: Eloquent で updated_at を更新したくない
A: $user->timestamps = false; または const UPDATED_AT = null;。または $user->saveQuietly() でイベント抑止。

Q: 大量 UPDATE の進捗を分割したい
A: chunkById(1000, function ($rows) { ... }) で 1000 件ずつ処理、または BatchService 的なジョブ分割を推奨。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. SELECT
  2. INSERT
  3. UPDATE
  4. DELETE
  5. order by句のキャスト
  6. count / max / average (集計)
  7. 配列を条件にする方法
  8. where句の入れ子(ネスト)

最近更新/作成されたページ