#テーブル#
ゲームから見てメーカーは多対1の関係(belongsTo)レビューは1対多(hasMany)の関係。
class GamesTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('Reviews');
$this->belongsTo('Makers');
}
}
#selectしようとしたらなんかエラー出たんだけど!#
SQLを発行するときに、データをselectするのはいい考えですね。
クエリビルダーはselectメソッドを使わない場合、全てのカラムがselectされますが、
パフォーマンスを考えると必要なカラムだけ取得したいものです。
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
class GameController extends AppController
{
public function index()
{
$reviews_table = TableRegistry::get('Games');
$review = $reviews_table->find()
->contain('Makers', 'Reviews')
->select([
'title' => 'Games.title',
'price' => 'Games.price',
'name' => 'Makers.name',
'comment' => 'Reviews.comment'
]);
}
}
しかし、Reviews.commentなんてカラムはないよって怒られます。
でも同じアソシエーションしてるMakersテーブルからは持ってこれてるじゃん!なんで!?
これは、belongsToとhasManyとで発行しているSQLの違いにあります。
クエリオーオブジェクトをdebug()すると、データベースで発行してるSQLを見ることができます。
SELECT Games.title AS title, Games.price AS price, Makers.name AS name
FROM Games
LEFT JOIN Makers
ON Games.id = Makers.Game_id;
SELECT Reviews.comment
FROM Revies
WHERE Game_id IN (1, 2, 3); /* このIN句の数字は上記のSQLで発行されたGamesテーブル主キー*/
だいたいこんな感じのSQLが発行されます。
belongsToで関連付けられたテーブルは、LEFT JOINが使用されていることがわかりますね。
hasManyで関連付けれたテーブルは、複数のSQLに分割して発行されます。
ちなみに4つのアソシエーションがどのようなSQLを発行するかはこんな感じ。
アソシエーション種別 | 発行されるSQL |
---|---|
hasOne | LEFT JOIN |
belongsTo | LEFT JOIN |
hasMany | 分割 |
belongsToMany | 分割 |
##つまりどういうことだってばよ!?##
クエリビルダーで使ったselectメソッドは、1つ目のSQL文に対して適応されます。
一目瞭然ですが、1つ目に発行されたSQL分にはReviews.comment
なんてカラムはかけらも見当たらないですね。
だからCakePHPちゃんは「お前の指定したカラム見つかれねえんだけど!!」とお怒りだったわけです。
##それじゃあhasManyやbelongsToManyでSELECTしたいときは?##
containにクロージャーを渡してあげます。
$review = $reviews_table->find()
->contain(['Makers', 'Reviews' => function ($q) {
return $q->select([
'Game_id',
'comment'
]);
}])
->select([
'title' => 'Games.title',
'price' => 'Games.price',
'name' => 'Makers.name',
]);
]);
ここで注意したいのは、関連テーブルを**selectする際には、外部キーを確実にselectする必要がある。**ということです。
外部キーは規約にしたがってると、ここではGame_idになります。
公式にもそう書いてありますね。
関連によってフェッチされるフィールドを限定する場合、外部キーの列が確実に select されなければなりません 。外部キーのカラムが select されない場合、関連データが 最終的な結果の中に無いということがおこります。
って書いてあるけど外部キーがselectされてなかったエラー吐いてくれます。
##主キー側も(binding key)もselectされてなければならない!!##
それよりもやっかいなのはこっち。
主キーつまり、Gamesテーブルのidをselectしていないと、エラーを吐かないでただただhasMany関連が空で取得されます。
結構気づきくい。
まあこれは、hasManyで発行されるSQLを見ればなんでそうなるかなんとなく理解できると思います。
最終的にクエリビルダーはこうなる。
$review = $reviews_table->find()
->contain(['Makers', 'Reviews' => function ($q) {
return $q->select([
'Game_id'
'comment' => 'Reviews.comment'
]);
}])
->select([
'id',
'title' => 'Games.title',
'price' => 'Games.price',
'name' => 'Makers.name',
]);
]);
##もう一個ハマリポイント##
主キーをselectしなきゃいけないけど、MakersテーブルとはLEFT JOIN
してるから、idはアンビシャスになるんだろうなーって思いつつこんな書き方したら、
->select([
'id' => 'Games.id'
'title' => 'Games.title',
'price' => 'Games.price',
'name' => 'Makers.name',
]);
だめだった。これもhasManyを取得できないです。
selectしなかった時にSQLログを見ると、Games.id AS Games__id
みたいな書き方されてたから、
->select([
'Games__id' => 'Games.id'
'title' => 'Games.title',
'price' => 'Games.price',
'name' => 'Makers.name',
]);
って感じにしたらうまくいきました。
というか、そもそもさっきみたいにidをselectしてもアンビシャスにならないっぽいですね。