LoginSignup
8
7

More than 3 years have passed since last update.

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

Posted at

テーブル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先を指定するときは、必ず主キーも入れよう。

8
7
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
8
7