🚨
Laravel Eloquentのwithでの注意点
Laravel withの絞り込みはwith先だけ
Laravelでよく利用する機会のあるwith
ですが、このwith
に対する理解が浅いと思わぬ結果を引き起こしてしまいます。
問題
例えば下記のようなクエリがあるとします
$posts = Post::with(['user' => function ($query) {
$query->where('role', '=', RoleType::ADMIN);
}])
->get();
感覚的には RoleType::ADMIN
のものだけのPostが取得されるように見えますが、実際にはpostsは全て取得され、roleがAdmin以外のものは$posts->userがnullで返却されます。
with先だけwhereしてしまい、結果的には全て取ってしまうことになります。
解決策
whereHasを使う
$posts = Post::whereHas('users', function ($query) {
$query->where('role', '=', RoleType::ADMIN);
})
->with('user')
->get();
デメリットとしてはwhereHas句はExists句を発行するため、参照するデータが大きいケースだとslow queryになってしまう可能性があるということです。
whereInを使う
$posts = Post::whereIn('user_id', function ($query) {
$query->select('id')
->from('user_id')
->where('role', '=', RoleType::ADMIN)
})
->with('user')
->get();
whereInでid側を絞り込み、サブクエリの方でwhereを行うことでインデックスも使えるようになります。
whereHasはslow queryになる可能性を含んでいますが、こちらはスピードの面では高速になります。
whereInでqueryを使う(やりすぎ)
$posts = Post::whereIn('user_id', User::query()
->where('role', '=', RoleType::ADMIN)
->pluck('id')
)
->with('user')
->get();
以上のようなクエリを複数使うという過激なアプローチもありますが、ここまでは必要ないと思っています。
少し複雑度が上がるのでバランスを考えると上記のwhereIn程度でとどめておく方がちょうどよいのかと感じました。
まとめ
- withでのwhereはwith先だけしかwhereしかしないので、nullが返る
- whereInを使ってサブクエリを発行する方がwhereHasより高速になる
- テストケースを漏れなく作り、考慮漏れがないか検証を疎かにしない
Discussion