やりたいこと
例えば usersテーブルとblogsテーブルが1対多の関係にあり、全てのユーザー情報と、ユーザーが書いたブログのうちcategoryがPHPのブログ情報をまとめて取得したい、というような場面。
Laravelでは User::with('blogs')
のように記述することでリレーション先のテーブルのデータもまとめて取得することが出来るが、その際に取得するリレーション先のデータの条件指定の方法について、備忘録としてまとめておく。
記述方法
結論、以下の記述でやりたいことが実現できる。
$items = User::with(['blogs' => function ($query) {
$query->where('category', 'PHP');
}])->get();
実行しているSQLを出力して確認してみる。
DB::enableQueryLog();
$items = User::with(['blogs' => function ($query) {
$query->where('category', 'PHP');
}])->get();
Log::debug(DB::getQueryLog());
SQL出力結果
array (
0 =>
array (
'query' => 'select * from `users`,
'bindings' =>
array (
),
'time' => 9.9,
),
1 =>
array (
'query' => 'select * from `blogs` where `blogs`.`user_id` in (1, 2, 3, ...省略
上記の通り、ユーザー情報は全て取得し、ブログについてはcategoryがPHPのもののみ取得できていることが分かる。
失敗例
ちなみに以下の書き方だと、whereHasの条件に合うユーザー情報を取得する。
よって今回のように、ユーザー情報は全て取得したうえで、リレーション先のデータは指定した条件に合うものだけ取得したい、、という場合は以下の書き方ではいけないので注意。
$items = User::with(['blogs'])->whereHas('blogs', function ($query) {
$query->where('category', 'PHP');
}])->get();
こちらも実行しているSQLを出力して確認してみる。
DB::enableQueryLog();
$items = User::with(['blogs'])->whereHas('blogs', function ($query) {
$query->where('category', 'PHP');
}])->get();
Log::debug(DB::getQueryLog());
SQL出力結果
array (
0 =>
array (
'query' => 'select * from `users` where exists (select * from `blogs` where `users`.`id` = `blogs`.`user_id` and `category` = ?),
'bindings' =>
array (
0 => 'PHP',
),
'time' => 20.21,
),
1 =>
array (
'query' => 'select * from `blogs` where `blogs`.`user_id` in (1, 4, 5, ...省略
上記の通り、existsでサブクエリを使って、リレーション先で指定した条件に合致するユーザー情報とそのリレーション先のデータを取得していることが分かる。
さいごに
実行されているSQLを確認すると、何が起きているのか分かりやすかった。
普段からEloquentを使うときにはどのようなSQLが実行されるのか把握し、SQLの実行計画も確認して重くなっていないか意識するようにしたい。
参考記事