Laravelを使って開発をする際に、親子関係にあるテーブルのデータをまとめて取得したいという場面があります。
今回はこのようなときに便利なwith関数(Eagerロード)についてご説明します。
Eagerロードとは何か?
関連する複数のテーブルのデータをまとめて取得する場合に、
- Eager Loading(熱望的なローディング…?)
- Lazy Loading(怠惰なローディング…?)
の2種類の方法があります。
例えば、下記のような2つのテーブルがあり、請求書Aとその明細のデータを取得するとします。
Lazy Loadingでは、
- まず請求書テーブルから請求書Aのデータを取得
- 次に明細テーブルから関連する明細データを取得
というように2回に分けてデータを取得します。
これに対してEager Loadingでは、2つのテーブルのデータを1度に取得します。
Eager Loadingを使うと、クエリの発行回数を減らすことができるので、いわゆる「N+1問題」の回避策として用いられます。
N+1問題とは?
クエリの発行回数が過剰に増えて、サーバーサイドでタイムアウトエラーなどの不具合を起こしてしまう現象をいいます。
leftJoin(左内部結合)との使い分け
with関数以外にも、leftJoin関数を利用することで、関連テーブルのデータをまとめて取得することができます。
それでは、with関数とleftJoin関数は、どのように使い分ければ良いのでしょうか?
2つの関数の特徴について表にまとめると下記のようになります。
leftJoin関数は関連データをフラットな形で取得するので、親子関係が1対1のとき(ケース①)は問題ありませんが、1対多のとき(ケース②)は親側にダブりが出てしまいます。
これに対して、with関数は関連データをネストした形で取得するので、親子関係が1対多のときでもダブリなく取得できるのです。
なので、親子関係が1対1のときはleftJoin関数、1対多のときはwith関数を使うと覚えておくといいかもしれません。
with関数の使い方とできること
使い方
with関数を使うときは、次の2ステップを踏みます。
①モデルにリレーションを定義する
public function invoice_detail() {
return $this->hasMany('App\Models\InvoiceDetail');
}
②データを取得するときにwith関数を使う
Invoice::with('invoice_detail')->where('id', $invoice_id)->first();
できること
with関数は単に小テーブルのデータを取得するだけではなく、下記のようなこともできます。
複数の小テーブルからデータを取得する
複数のリレーションをカンマ(,)で区切って書きます。
Invoice::with([invoice_detail,relation_slip])->where('id', $invoice_id)->first();
特定のカラムのデータだけ取得する
リレーションの後にコロン(:)を付けて、続けてカラム名をカンマ(,)で区切って書きます。
Invoice::with('invoice_detail:id,name')->where('id', $invoice_id)->first();
取得するデータに条件を付けたり、並び替えたりする
Invoice::with('invoice_detail')->where('id', $invoice_id)->first();
孫テーブルのデータも併せて取得する
リレーションの後にピリオド(.)を付けて、続けて孫のリレーションを書きます。
Invoice::with('invoice_detail.')->where('id', $invoice_id)->first();