Quantcast
Channel: rana_kualuの記事 - Qiita
Viewing all articles
Browse latest Browse all 331

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

$
0
0

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


Viewing all articles
Browse latest Browse all 331

Trending Articles