テーブルBと、B.idにリレーションを張ってるテーブルAがあったとします。
モデルは普通。
// モデルAclassTableAextendsModel{// テーブル名protected$table='table_a';/**
* リレーション
* @return BelongsTo
*/publicfunctiontableb(){return$this->belongsTo(TableB::class,'b_id','id');}}// モデルBclassTableBextendsModel{// テーブル名protected$table='table_b';}
コントローラとテンプレも普通。
$tableas=TableA->get();
@foreach($tableasas$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にはスコープという機能があります。
// モデルAclassTableAextendsModel{// テーブル名protected$table='table_a';/**
* リレーション
* @return BelongsTo
*/publicfunctiontableb(){return$this->belongsTo(TableB::class,'b_id','id');}/**
* 取得対象カラム
*/publicstaticfunctionscopeName($query){return$query->select(['name']);}}// モデルBclassTableBextendsModel{// テーブル名protected$table='table_b';/**
* 取得対象カラム
*/publicstaticfunctionscopeName($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')
という例示のせいで一見同じものに見えないため、関係ないと読み流してしまいました。
ということできちんと結合するには、主キーも取ってくるようにしましょう。
// モデルBpublicstaticfunctionscopeName($query){return$query->select(['id','name']);}
まとめ
SELECT先を指定するときは、必ず主キーも入れよう。