背景
LaravelのORMであるEloquentを使用して、対象を絞り込もうとした際に意図した条件のレコードが取得できない事がありました。
具体的には、
①「親テーブルの全レコード」
②「それに紐づいた子テーブルのレコード」
の上記2つを絞り込む意図がありました。しかし、whereHas句を使用したところ意図した結果が返ってこなかったため、with句を使ったEager Loadingでの解決方法を「ゲームテーブル」と「ゲームレビューテーブル」を例に解説します。上記①と②のような条件の絞り込みをするときに参考にしていただければと思います。
テーブル例
以下のようなテーブルがあるとします。
親テーブル
1. ゲームテーブルgames
id | title | genre | release_date | created_at | updated_at |
---|---|---|---|---|---|
1 | スーパーアドベンチャー | Action | 2023-01-15 | 2023-01-01 10:00 | 2023-01-01 10:00 |
2 | パズルワールド | Puzzle | 2023-05-20 | 2023-05-01 10:00 | 2023-05-01 10:00 |
子テーブル
2. ゲームレビューテーブルgame_reviews
id | game_id | rating | review | created_at |
---|---|---|---|---|
1 | 1 | 3 | 悪くない | 2023-02-11 10:00 |
2 | 2 | 4 | 満足です | 2023-06-02 12:00 |
実現したいこと
- ゲームテーブルから取って来たいレコード
-
id = 1
のアドベンチャーワールド -
id = 2
のパズルワールド
-
- ゲームレビューテーブルから取って来たいレコード
-
'rating' = 4
のレコード
-
解決方法
実現できなかったwherehasを使ったクエリ(✖)
Games::whereHas('game_reviews', function ($query) {
$query->where('rating', 4);
})->get();
whereHasクエリでは子レコードの'rating'が4である親レコードのみを取得します。
id | title | genre | release_date | created_at | updated_at |
---|---|---|---|---|---|
2 | パズルワールド | Puzzle | 2023-05-20 | 2023-05-01 10:00 | 2023-05-01 10:00 |
- ゲームテーブルから取得できるレコード
✖︎id = 1
のアドベンチャーワールド-
id = 2
のパズルワールド(子レコードのratingが4のため) ◯
- ゲームレビューテーブルから取得できるレコード
✖︎'rating' = 4
のレコード
実現できたwithを使ったクエリ(⚪︎)
$query = Games::with([
'game_reviews' => function ($query) {
$query->where('rating', 4);
}
])->get();
withを使ったEager Loadingでは、親テーブルを全件+rating=4
の子テーブルのレコードを取得します。
id | title | genre | release_date | created_at | updated_at |
---|---|---|---|---|---|
1 | スーパーアドベンチャー | Action | 2023-01-15 | 2023-01-01 10:00 | 2023-01-01 10:00 |
2 | パズルワールド | Puzzle | 2023-05-20 | 2023-05-01 10:00 | 2023-05-01 10:00 |
game_id | rating | review | created_at |
---|---|---|---|
2 | 4 | 満足です | 2023-06-02 12:00 |
- ゲームテーブルから取得できるレコード
id = 1
のアドベンチャーワールド ◯-
id = 2
のパズルワールド ◯
- ゲームレビューテーブルから取得できるレコード
'rating' = 4
のレコード ◯
リレーション先のメソッドを直接呼び出さずにwithを使う理由
リレーション先を呼び出す方法として「game->gameReview」のように定義したメソッドから関連レコードを呼び出す方法(Lazy Loading)があります。しかし例えばfor文で複数レコードを取って来たいときに、これではループの回数分クエリを発行してしまうためデータベース的に効率が悪いです。なのでwithを使って一括でデータベースからデータを取ってきて処理をします。これによりデータベース間のやり取りは1回ですみます。
まとめ
以上のように、目的のレコードを取得することができました。
個人的にwithは「共に」という意味があるので、レコードが追加でローディングされて、whereHasは「持つ」という意味があるため条件を持ったレコードのみ返される、という理解をしました。↓はwhereHasとwithの違いのまとめです。
whereHasとwithの違い
whereHas
目的
リレーション先の条件に基づいて、元テーブルのデータを絞り込む。
特徴
- 元テーブルのデータをリレーション先の条件でフィルタリングする
- 元テーブルのレコードは、リレーション先の条件を満たす場合のみ取得される
- 条件に合致したリレーション先テーブルのデータは、取得結果には含まれない(フィルタリングにのみ使用)
with
目的
元テーブルのデータを取得しつつ、リレーション先のデータもロードする
特徴
- 元テーブルのデータは、条件に関係なくすべて取得される
- リレーション先のデータには、指定した条件を適用してロード(Eager Loading)する
- 元テーブルを取得しながら、条件付きのリレーション先テーブルのデータを必要に応じて追加ロードできる
参考
EagerLoadingについて