K.Takahashiをフォローする

Laravelのwith関数(Eagerロード)でクエリチューニング

バックエンド

Laravelを使って開発をする際に、親子関係にあるテーブルのデータをまとめて取得したいという場面があります。

今回はこのようなときに便利なwith関数(Eagerロード)についてご説明します。

Eagerロードとは何か?

関連する複数のテーブルのデータをまとめて取得する場合に、

  • Eager Loading(熱望的なローディング…?)
  • Lazy Loading(怠惰なローディング…?)

の2種類の方法があります。

例えば、下記のような2つのテーブルがあり、請求書Aとその明細のデータを取得するとします。

Lazy Loadingでは、

  1. まず請求書テーブルから請求書Aのデータを取得
  2. 次に明細テーブルから関連する明細データを取得

というように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();