さて、雑誌(magazines)と出版社(publishers)を Model::$belongsTo で連結させることができましたが、今度は都道府県(states)と出版社を連結させて、3つのテーブルを使います。
実用的な問題として、ひとつのテーブルで収まることはまず無い訳で、ログやら単純なマスターテーブル程度ならばいいのですが、【真面目に】テーブルを正規化していれば、3 つ以上のテーブルが連結するのは当たり前ですッ!!! … だといいなぁ、という希望的観測も含めて(苦笑)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | -- 雑誌テーブル create table magazines ( id int not null , name varchar (50), publisher_id int , price int ); -- 出版社テーブル create table publishers ( id int not null , name varchar (50), state_id int ); -- 都道府県テーブル create table states ( id int not null , name varchar (20) ); |
# 説明が面倒なので、T100 を magazines に、T200 を publishers に変更しましたw
ER 図はこんな感じで、1対多の関係です。
これを出版社を中心にして(cakephp は left join で連結させるので)、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <h2>出版社一覧</h2> <table> <tr> <td>id</td> <td>name</td> <td>Magazine.name</td> <td>State.name</td> </tr> <?php foreach ( $Publishers as $item ) : ?> <tr> <td><?php echo $item [ 'Publisher' ][ 'id' ] ?></td> <td><?php echo $item [ 'Publisher' ][ 'name' ] ?></td> <td><?php echo $item [ 'Magazine' ][ 'name' ] ?></td> <td><?php echo $item [ 'State' ][ 'name' ] ?></td> </tr> <?php endforeach ; ?> </table> |
のように、都道府県名(State)を表示させようとすると、Publisher モデルクラスに次のように加筆します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- models/publisher.php --> <?php class Publisher extends AppModel { var $name = 'Publisher ' ; var $belongsTo = array ( 'Magazine' => array ( 'className' => 'Magazine' , 'conditions' => 'Magazine.publisher_id = Publisher.id' , 'order' => 'Publisher.id ASC' , 'foreignKey' => '' ), 'State' => array ( // ここから追加 'className' => 'State' , 'conditions' => '' , 'order' => 'State.id ASC' , 'foreignKey' => 'state_id' ) ); } ?> |
関係が
- Publisher->Magazine
- Publisher->State
なので、1段階しか探索をしないため結構簡単にできます。
実行すると素直にデータが取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | SELECT `Publisher`.`id`, `Publisher`.` name `, `Publisher`.`state_id`, `Magazine`.`id`, `Magazine`.` name `, `Magazine`.`publisher_id`, `Magazine`.`price`, `State`.`id`, `State`.` name ` FROM `publishers` AS `Publisher` LEFT JOIN `magazines` AS `Magazine` ON (`Magazine`.`publisher_id` = `Publisher`.`id`) LEFT JOIN `states` AS `State` ON (`Publisher`.`state_id` = `State`.`id`) WHERE 1 = 1 ORDER BY `Publisher`.`id` ASC , `State`.`id` ASC |
発行されるクエリを見ると、left join でうまく繋がっています。
難点を云えば、
- left join でつながっているので、雑誌を持たない出版社も表示されている。この場合、inner join にどうすれば切り替えられるのかは不明です。
- 全てのカラムを取得しているので、カラム内のデータが多い場合(varchar(5000)など)は、データ転送に時間がかかる。できれば、index.ctp で使っているカラムだけのクエリを作って欲しいのですが、これもどうやるのか不明です。
そんな感じで、クエリ文を直接書いてしまえば良いのでは?と思ったりしますが、PHP は分かるけど、SQL は分からない(C# でも LINQ は分かるけど SQL が分からない、サブクエリが書けない、という方もいらっしゃるので)場合もあるので、これはこれで良いのかなと。
# 余談ですが、Model::$hasMany を使うとクエリ文がたくさん発行されてしまうという…これって、サブクエリを使えばいいんじゃないの?と思うのですが、パフォーマンス的にはどうなんでしょう。CakePHP 2.0 だと違うのかな?
次に雑誌(Magazine)を中心にして、連結をさせます。
- Magazine->Publiser
- Magazine->Publiers->State
という形で Magazine から State まで 2段階の探索が必要になるわけですが。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- models/magazine.php --> <?php class Magazine extends AppModel { var $name = 'Magazine' ; var $belongsTo = array ( 'Publisher' => array ( 'className' => 'Publisher' , 'conditions' => '' , 'order' => 'Magazine.id ASC' , 'foreignKey' => 'publisher_id' ), 'State' => array ( // ここから追加 'className' => 'State' , 'conditions' => 'Publisher.state_id = State.id' , 'order' => 'State.id ASC' , 'foreignKey' => '' ), ); } ?> |
こんな風に conditions に連結の条件を指定してしまいます。
本当は、Model::$recursive を設定するのでしょうが…
recursive :: Model の属性 :: モデル :: CakePHPによる開発 :: マニュアル :: 1.2コレクション :: The Cookbook
http://book.cakephp.org/ja/view/439/recursive
recursiveの正しい理解CakePHP – CPA-LABテクニカル
http://www.cpa-lab.com/tech/081
なんか思ったように動いてくれません。なので、手っ取り早い方法としては、Model::$belongsTo に設定してしまうのがお手軽のなのかなぁと。
このようにモデルで設定しておくと、次のように $item[‘State’][‘name’] で都道府県名が表示できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <h2>雑誌一覧</h2> <table> <tr> <td>id</td> <td>name</td> <td>Publisher.name</td> <td>State.name</td> </tr> <?php foreach ( $Magazines as $item ) : ?> <tr> <td><?php echo $item [ 'Magazine' ][ 'id' ] ?></td> <td><?php echo $item [ 'Magazine' ][ 'name' ] ?></td> <td><?php echo $item [ 'Publisher' ][ 'name' ] ?></td> <td><?php echo $item [ 'State' ][ 'name' ] ?></td> </tr> <?php endforeach ; ?> </table> |
発行されるクエリは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | SELECT `Magazine`.`id`, `Magazine`.` name `, `Magazine`.`publisher_id`, `Magazine`.`price`, `Publisher`.`id`, `Publisher`.` name `, `Publisher`.`state_id`, `State`.`id`, `State`.` name ` FROM `magazines` AS `Magazine` LEFT JOIN `publishers` AS `Publisher` ON (`Magazine`.`publisher_id` = `Publisher`.`id`) LEFT JOIN `states` AS `State` ON (`Publisher`.`state_id` = `State`.`id`) WHERE 1 = 1 ORDER BY `Magazine`.`id` ASC , `State`.`id` ASC |
表示が若干違うのは、left join をしているからですね。
v1.3.x ならモデル側で「var $actsAs = array(‘Containable’);」を設定すれば以下のコードでマガジンから出版社とその都道府県が取得出来ます。
もちろん、通常のアソシエーション設定は必要。
コントローラで:
$magazine = $this->Magazine->find(‘first’,array(
‘contain’ => array(‘Publisher’, ‘Publisher.State’),
));
v1.2系は不明。
(一個前の別のコードが混入、、、削除していただければ助かります。)
削除、ありがとうございました。
学習しているのが、v1.3.x なので、var $actsAs = array(‘Containable’); を試してみますね。
結果は後ほど。