Posted at

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

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