Help us understand the problem. What is going on with this article?

[Laravel] Eloquent リレーションと Eager Loading

More than 1 year has passed since last update.

「クエリが大量に発行されているせいでタイムアウトしちゃってる」
といったことを避けるために重要な"Eager loading"。

Eloquent リレーションで Eager loading するにはなんとなく with メソドを使うということぐらいしか知らなかったので、
ドキュメントの Eloquent: Relationships > Eager loading の部分を読んでみました。

参考

公式ドキュメント
https://laravel.com/docs/5.6/eloquent-relationships#eager-loading

Eloquent のリレーション

EloquentのリレーションはEloquentモデルにメソドで定義をする。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

呼び出すときは

$user->posts()->where('active', 1)->get();

という感じ。
リレーションメソドではクエリビルダが使える。

リレーションメソド と 動的プロパティ

リレーションクエリに制約を追加する必要がない場合、プロパティのように -> でリレーションにアクセスすることができ、リレーションメソドに対してこれは「動的プロパティ」という。

$user = App\User::find(1);

//->posts() じゃなく、 ->posts になってるのが動的プロパティ
foreach ($user->posts as $post) {
    //
}

リレーションデータの Eager Loading

  • 全部のデータをとってきて、
  • (動的)プロパティで毎回リレーションデータをとってくる。

というようにしてしまうと、レコード数分だけクエリが発行されてしまう。

//lazy loading
$books = App\Book::all(); //クエリを1回発行したあと、

foreach ($books as $book) {
    echo $book->author->name; //booksの冊数分(N回)だけ発行される。
}

よく言う「N+1」問題というやつ。
クエリを1回+N回発行してしまうと、大規模なデータになるほど、データをとるのにものすごく時間がかかってしまってタイムアウトになってしまうこともある。

これを避けるため、先にリレーションデータも取得しておくのが "Eager loading" であり、Eloquentでは with メソドを使う。

//Eager loading
$books = App\Book::with('author')->get(); //クエリが発行されるのは2回だけ

foreach ($books as $book) {
    echo $book->author->name;
}

こうやって with メソドを使えば、"Eager loading" でリレーションデータを取得することができ、クエリの発行はたった2回で済む。

リレーションメソドを使ってしまうと Eager loading にしてても意味がないので注意

$books = App\Book::with('author')->find(1);;
$author = $user->author()->where('id', 1)->get();

リレーションメソドの場合、先ほどの例のように ...->where('active', 1)->... とクエリビルダを使って制約が追加することができる。
ただし、毎回クエリを発行してデータを取りに行ってしまうことになるため、先にEager Loadingでリレーションデータを取ってきていても、そのあとにリレーションメソドを使ってしまうと意味がない。

一方の動的プロパティはキャッシュが聞くため、EloquentでEager Loadingをする場合は動的プロパティの方を使う。

参考
https://qiita.com/katsunory/items/d4757b8d9ac099a5291e

Eager Loading のいろんなやり方

複数のリレーションをとることもできる

with の引数に配列を使って、複数のリレーションデータをとることもできる。

$books = App\Book::with(['author', 'publisher'])->get();

ネストされたテーブルのリレーションだってとることができる

「リレーションされたテーブルのリレーション」というネストされたテーブルのデータもEager loadingで取得することができる。
これは知らんかった。今度から使おう。

$books = App\Book::with('author.contacts')->get();

特定のカラムだけをとることもできる

$users = App\Book::with('author:id,name')->get();

この場合、 id は必ずとる必要があることに注意。

Eager loadingするリレーションデータを限定したいとき

「titleにfirstを含むリレーションデータだけをEager Loadingしたい」というときは with の中でクロージャを使えば可能。

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

注意点

posts のクエリでデータがとれないユーザーは $users に入ってこないのかと思っていたが、その場合は posts がnullになるだけで $users には入ってくる。
with はリレーションデータをとってくるメソド)

「titleにfirstを含むリレーションデータを持たないユーザーはいらない」と、リレーションデータをとってきたいのではなく リレーションデータによる絞り込み をしたい場合は whereHas メソドを使う。

$users = App\User::whereHas('posts', function($query) {
    $query->where('title', 'like', '%first%');
})->get();

「先に親データとっちゃったけど、追加で子のデータもEager Loadingしたい」というのもできる

「すでに親のデータだけとっちゃったけど、リレーションデータをEager loadingでとりたい」というときには load メソドを使う。
(ドキュメントでは"Lazy Eager loading"と表記されている)

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

load メソドも with と同様、追加でクエリを発行することが可能。

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);
Eager loadし忘れたやつだけとる、という loadMissing メソドもある
public function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

all メソドで実装しちゃった...」という経験は何度かあるので、この load loadMissing メソドは覚えておくと便利そう。

まとめ

  • Eloquentのリレーションはリレーションメソドを使う
    • リレーションメソドではクエリビルダ、チェーンメソドが使える。
  • 制約を追加する必要がないのであれば動的プロパティとしてアクセスすることができる
  • EloquentでEager Loadingする場合は with メソドを使って、データは動的プロパティでとってくる
  • EloquentにはEager Loadingする手段がいくつも用意されている
    • あとからEager Loadとかもできる
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away