14
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelでの関連データ取得:withとwhereHasの使い分け

Last updated at Posted at 2024-12-05

背景

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について

14
2
0

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
14
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?