4.

Laravel Query Builder の DELETE 完全ガイド(Eloquent / Soft Delete / 一括削除)

編集
この記事の要点
  • Query Builder DELETE: DB::table("users")->where("id", 1)->delete()
  • Eloquent DELETE: $model->delete() / User::destroy([1, 2, 3])
  • Soft Delete: SoftDeletes trait + deleted_at 列で論理削除
  • 強制削除: $model->forceDelete()(Soft Delete でも物理削除)
  • truncate: DB::table("t")->truncate() でテーブル全消し(AUTO_INCREMENT もリセット)
  • 外部キー制約に注意。トランザクション + onDelete cascade 設計

Query Builder での DELETE

Laravel の Query Builder は DB ファサード経由で SQL ライクに DELETE を発行します。条件を where() で組み立てて delete() で実行します。

use Illuminate\Support\Facades\DB;

// 単一レコードを削除
DB::table('users')->where('id', 1)->delete();
// → DELETE FROM users WHERE id = 1

// 複数条件
DB::table('users')
    ->where('active', false)
    ->where('last_login_at', '<', now()->subYear())
    ->delete();
// → 戻り値は削除された行数 (int)

// IN 条件
$deleted = DB::table('users')
    ->whereIn('id', [1, 2, 3])
    ->delete();
echo "$deleted 件削除しました";

// 全件削除(テーブルは残す、where 無し)
DB::table('temp_logs')->delete();

Eloquent モデル経由の DELETE

Eloquent ORM ではモデルインスタンスの delete() メソッドで削除します。モデルイベント(deleting / deleted)も発火します。

use App\Models\User;

// インスタンスから削除
$user = User::find(1);
$user->delete();

// 主キーで一括削除(destroy)
User::destroy(1);
User::destroy([1, 2, 3]);
User::destroy(1, 2, 3);
// → 戻り値は削除された件数

// クエリ経由
User::where('active', false)->delete();

// チャンク削除(大量データ向け)
User::where('created_at', '<', now()->subYears(5))
    ->chunkById(1000, function ($users) {
        foreach ($users as $user) {
            $user->delete();
        }
    });

Soft Delete (論理削除)

Soft Delete は実際には DELETE せず、deleted_at カラムに削除日時を入れる「論理削除」です。誤削除の取り消し、監査ログ用途に便利。

// migration: deleted_at カラム追加
Schema::table('users', function (Blueprint $table) {
    $table->softDeletes();   // → deleted_at TIMESTAMP NULL
});

// Model: SoftDeletes trait を使う
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use SoftDeletes;
}

// 削除 → deleted_at に日時セット
$user->delete();
// → UPDATE users SET deleted_at = NOW() WHERE id = 1

// 通常クエリは deleted_at IS NULL 自動付加
User::all();
// → SELECT * FROM users WHERE deleted_at IS NULL

// 削除済を含む
User::withTrashed()->get();

// 削除済のみ
User::onlyTrashed()->get();

// 復元
$user = User::withTrashed()->find(1);
$user->restore();

// 物理削除(DELETE 文発行)
$user->forceDelete();

DELETE 系メソッド比較

メソッド動作イベント戻り値
DB::table()->delete()SQL DELETE無し(DB ファサード)削除件数
$model->delete()Eloquent DELETE(Soft Delete 有効時は UPDATE)deleting / deletedbool
Model::destroy([id...])主キー一括削除deleting / deleted削除件数
Model::where()->delete()条件一括削除★ モデルイベント発火しない削除件数
$model->forceDelete()Soft Delete でも物理削除forceDeleting / forceDeletedbool
DB::table()->truncate()テーブル空にする + AUTO_INCREMENT リセット無し無し

TRUNCATE と DELETE の違い

// DELETE: 行ロック、WHERE 可、トランザクションでロールバック可
DB::table('logs')->delete();
// → DELETE FROM logs;
// → AUTO_INCREMENT は維持

// TRUNCATE: テーブル全削除、AUTO_INCREMENT リセット
DB::table('logs')->truncate();
// → TRUNCATE TABLE logs;
// → トランザクション内でも一部 DB でロールバックできない
// → 外部キー参照されているとエラー
項目DELETETRUNCATE
WHERE 句使える使えない(全削除)
AUTO_INCREMENT維持リセット
トランザクションロールバック可DB によって不可(MySQL は不可)
速度遅い(行ごと)★ 高速(テーブル再作成)
トリガー発火発火しない
外部キー制約違反時エラー★ 外部キー参照あるとエラー

LIMIT 付き DELETE

MySQL は DELETE ... LIMIT 構文に対応。Laravel の Query Builder では limit() + delete():

// 古いログを 1000 件ずつ削除(DB 負荷分散)
do {
    $deleted = DB::table('logs')
        ->where('created_at', '<', now()->subMonths(6))
        ->limit(1000)
        ->delete();
} while ($deleted > 0);

// SQL: DELETE FROM logs WHERE created_at < ? LIMIT 1000

トランザクションで安全に削除

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    // 関連テーブルから先に削除
    DB::table('order_items')->where('order_id', 100)->delete();

    // 親テーブル削除
    DB::table('orders')->where('id', 100)->delete();
});

// 失敗時は自動ロールバック
try {
    DB::beginTransaction();
    User::find(1)->delete();
    Profile::where('user_id', 1)->delete();
    DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
    throw $e;
}

外部キー制約 (Foreign Key) と Cascade

削除しようとする行を別テーブルが参照している場合、外部キー違反でエラーになります。設計段階で onDelete 動作を決めます:

// migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')
          ->constrained()
          ->cascadeOnDelete();   // ユーザー削除で関連 post も削除
    // 他の選択肢:
    // ->restrictOnDelete()  // 子があれば削除拒否(デフォルト相当)
    // ->nullOnDelete()      // user_id を NULL に
    // ->noActionOnDelete()  // DB 任せ
});

// エラー例
// SQLSTATE[23000]: Integrity constraint violation:
//   1451 Cannot delete or update a parent row:
//   a foreign key constraint fails

FAQ

Q: Model::where()->delete() で deleting イベントが発火しない
A: クエリビルダー扱いになるためです。イベントを発火させたいなら get() でモデル取得後に foreach + delete() するか、chunk() を使ってください。

Q: Soft Delete モデルを物理削除したい
A: $model->forceDelete() または Model::onlyTrashed()->forceDelete()deleted_at IS NOT NULL の行を一括物理削除。

Q: delete() の戻り値が 0 でも例外が出ない
A: 仕様です。「該当行が無かった」「すでに削除済」を区別したいなら、削除前に exists() 確認するか、findOrFail() + delete() を使ってください。

編集
Post Share
子ページ

子ページはありません

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

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