Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

【Laravel5.8】Eagerロード先テーブルは必ず主キーもSELECTしないといけない

More than 1 year has passed since last update.

テーブルBと、B.idにリレーションを張ってるテーブルAがあったとします。

モデルは普通。

モデル
// モデルA
class TableA extends Model{
    // テーブル名
    protected $table = 'table_a';

    /**
     * リレーション
     * @return BelongsTo
     */
    public function tableb()
    {
        return $this->belongsTo(TableB::class, 'b_id', 'id');
    }
}

// モデルB
class TableB extends Model{
    // テーブル名
    protected $table = 'table_b';
}

コントローラとテンプレも普通。

コントローラ
    $tableas = TableA->get();
blade
@foreach ($tableas as $tablea)
    {{ $tablea->name }}
    {{ $tablea->tableb['name'] }}
@endforeach

さくっとできました。

できたはいいけどn+1問題が直撃する書き方です。
ループのたびにSELECT * FROM table_b WHERE id=xというSQLが走るので、とてもよろしくありません。
よってEagerローディングするようにしましょう。
コントローラにwith書くだけなので一瞬です。

コントローラ
    $tableas = TableA->with('table_b')->get();

何十個も発行されていたSQLがわずか二つに減りました。

しかし、これもまだ無駄があります。
テーブルAもBもnameしか使ってないので、SELECT *ではなくSELECT nameのSQLを発行するようにしたいです。
このためにLaravelにはスコープという機能があります。

モデル
// モデルA
class TableA extends Model{
    // テーブル名
    protected $table = 'table_a';

    /**
     * リレーション
     * @return BelongsTo
     */
    public function tableb()
    {
        return $this->belongsTo(TableB::class, 'b_id', 'id');
    }

    /**
     * 取得対象カラム
     */
    public static function scopeName($query){
        return $query->select(['name']);
    }
}

// モデルB
class TableB extends Model{
    // テーブル名
    protected $table = 'table_b';

    /**
     * 取得対象カラム
     */
    public static function scopeName($query){
        return $query->select(['name']);
    }
}

コントローラからはスコープの呼び出しを追加します。

コントローラ
    $tableas = TableA::name()->with(['tableb' => function ($q) {$q->name();}])->get();

やったね。

はい、これテーブルBのデータ取って来れません。
リレーション先テーブルのデータに主キーがなかった場合、何故か紐付けてくれないのです。

実はEagerローディングは予想に反してJOINしていません。
発行されたSQLを見ると、SELECT name FROM table_a WHERE id IN (1, 2, 3); SELECT name FROM table_b WHERE id IN (4, 5, 6);みたいになっています。
別々に取ってきたデータを後でくっつけているので、くっつけるために主キーが必要になっています。

マニュアルにも特定カラムのEagerロードという項目で一応触れられているのですが、スコープとはだいぶ離れたところにあるうえにwith('author:id,name')という例示のせいで一見同じものに見えないため、関係ないと読み流してしまいました。

ということできちんと結合するには、主キーも取ってくるようにしましょう。

モデル
// モデルB
    public static function scopeName($query){
        return $query->select(['id', 'name']);
    }

まとめ

SELECT先を指定するときは、必ず主キーも入れよう。

rana_kualu
不労所得で生きたい。
https://twitter.com/rana_kualu
Why not register and get more from Qiita?
  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