LoginSignup
7
10

More than 3 years have passed since last update.

Laravelのchunk()で指定した条件を更新したい時の代替方法

Last updated at Posted at 2017-04-15

Laravelのchunkは便利だけど、クエリ条件を更新するような処理の場合に挙動がおかしくなり、切ないことになったので、そう言った時の代替案を考えてみた。

どんな問題が起きるかについては、すでに書いている方もいるが、一応書いておく。

Laravelでchunkする際の注意点

前提

  • タスクというデータを一括で完了にしたい
  • 完了はis_completedが1の状態の時

Chunkで起きる問題

is_completedが0のレコードを取得するような条件にしていて、クロージャー内でis_completedを更新すると更新分の取得件数が除外されてしまい、処理がずれてしまう。

不具合の起きるコード

// 未完了のタスクを3件ずつ取得して完了にする
Task::where('is_completed', 0)->chunk(3, function ($tasks) {
    foreach ($tasks as $task) {
        // 完了タスクに更新
        $task->is_completed = 1;
        $task->save();
    }
});

起きる不具合のイメージ

  • task1:最初のループで処理される
  • task2:最初のループで処理される
  • task3:最初のループで処理される
  • task4:1〜3が更新されたことによって、indexがずれ、2回めのループ対象にならない
  • task5:1〜3が更新されたことによって、indexがずれ、2回めのループ対象にならない
  • task6:1〜3が更新されたことによって、indexがずれ、2回めのループ対象にならない
  • task7:2回めのループ対象になり更新される。
  • task8:2回めのループ対象になり更新される。
  • task9:2回めのループ対象になり更新される。

task1〜3がクエリ条件から消えたので、task4〜6のindexがtask1〜3に昇格してしまった感じ。

代替案

limit()->get()->isEmpty() + whileにするだけ。これだけで対象のレコードに対して全件処理できる。

// 未完了のタスクを3件ずつ取得して完了にする
while(!($tasks = Tasks::where('is_completed', 0)->limit(3)->get())->isEmpty()) {
    foreach ($tasks as $task) {
        // 完了タスクに更新
        $task->is_completed = 1;
        $task->save();
    }
}

まとめ

  • 事前に対象のレコードをチェックせずにすむので、データ量を意識せずに解決できる
  • whileはこういう処理が向いている。

備考

  • 変数の初期化とか$loop=trueみたいなことをしても解決できるけど、めんどくさかった。
  • ただ単にwhileの条件や変数定義をワンライナーにしたかっただけ。
7
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10