単一のデータベーステーブルからデータを選択するためにアクティブレコード (AR) を使う方法を見てきました。 この章では、AR を使って、いくつかの関係するデータベーステーブルをつなげ、結合されたデータセットを取得する方法を示します。
リレーショナル AR を使う場合は、結合すべきテーブルに主キー・外部キー制約が宣言されていることが推奨されます。 この制約がリレーショナルデータの一貫性と整合性を保持するために役立ちます。
分りやすくするために、この章では例題として、以下のエンティティ関係 (ER) 図に示されるデータベーススキーマを使用します。
ER 図
情報: 外部キー制約のサポートは DBMS 毎に異ります。 SQLite 3.6.19 またはそれ以前のものは外部キー制約をサポートしませんが、テーブルを作成する際に制約を宣言することが出来ます。 MySQL の MyISAM エンジンは外部キーを全くサポートしません。
AR のリレーショナルクエリを使用する前に、AR に対して他の AR クラスとどのように関係しているかを知らせる必要があります。
二つの AR クラスのリレーションは、AR クラスによって表現されるデータベーステーブルのリレーションと直接関係しています。
データベースの観点からは、二つのテーブル A と B の関係には、3つのタイプがあります。
1対多 (例えば tb_user
と tbl_post
)、1対1 (例えば tbl_user
と tbl_profile
)、多対多 (例えば tbl_category
と tbl_post
)。
ARでは、以下の4種類のリレーションがあります。
BELONGS_TO
: テーブル A と B の関係が1対多ならば、B は A に属しています (例 Post
blongs to User
)。
HAS_MANY
: 同じくテーブル A と B の関係が1対多ならば、A は多くの B を持っています (例 User
has many Post
)。
HAS_ONE
: これは A がたかだか一つの B を持っている HAS_MANY
の特例です (例 User
has at most one Profile
)。
MANY_MANY
: これはデータベースにおいて多対多の関係と対応します。
多対多の関係は、1対多の関係に分割するために、関連付け用のテーブルが必要になります。
なぜなら大部分の DBMS は、直接には多対多の関係をサポートしないためです。
例題のデータベーススキーマでは、tbl_post_category
がこの目的のために使用されます。
AR 用語では、BELONGS_TO
と HAS_MANY
の組合せとして、MANY_MANY
を説明することができます。
例えば Post
は多くの Category
に属しています。そして Category
には多くの Post
があります。
5番目に、リレーションのレコードで集計クエリを実行する特殊なタイプがあります。
STAT
と呼ばれるリレーションです。
詳細については、統計クエリ を参照してください。
AR でのリレーション宣言は、CActiveRecord クラスの relations() メソッドをオーバライドすることで行います。 このメソッドはリレーション構成の配列を返します。 各々の配列要素は以下のフォーマットで示す一つのリレーションを意味します。
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...付加オプション)
ここで VarName
はリレーションの名前です。
RelationType
はリレーションのタイプを指定します。
そしてそれは4つの定数、self::BELONGS_TO
, self::HAS_ONE
, self::HAS_MANY
, self::MANY_MANY
のうちの一つです。
ClassName
はこの AR クラスに関係する AR クラスの名前です。
ForeignKey
はリレーションに関係する外部キーを指定します。
そして各リレーションについて、最後に付加オプションを指定することができます (後で説明します)。
以下のコードでどのように User
クラスと Post
クラスのリレーションを宣言するかを示します。
class Post extends CActiveRecord
{
......
public function relations()
{
return array(
'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
'categories'=>array(self::MANY_MANY, 'Category',
'tbl_post_category(post_id, category_id)'),
);
}
}
class User extends CActiveRecord
{
......
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}
情報: 外部キーは2個以上のカラムで構成される複合キーでもかまいません。 この場合は、外部キーのカラムの名前をカンマで区切って結合した文字列か、または array('key1','key2') のような配列を使わなければなりません。 標準的でない PK->FK 結合を指定する必要があるときは、array('fk'=>'pk') として定義することが出来ます。 複合キーの場合は、array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2') となります。
MANY_MANY
のリレーションにおいては、外部キーとして、関連付けテーブル名も指定されなければなりません。 例えば、Post
におけるcategories
リレーションは、tbl_post_category(post_id, category_id)
という外部キーにより指定されます。
AR クラスにおいてリレーションを宣言すると、各々のリレーションを表わす暗黙のプロパティがクラスに加えられます。
リレーショナルなクエリが実行された後、対応するプロパティは関連する AR の (単一または複数の) インスタンスで満たされます。
例えば、$author
が User
AR インスタンスを表している場合、$author->posts
を使って、関連した Post
インスタンスにアクセスすることが出来ます。
リレーショナルクエリを実行する最も単純な方法は、AR インスタンスのリレーショナルなプロパティを読み出すことです。 プロパティが以前にアクセスされていない場合には、リレーショナルクエリが開始されます。 このクエリは二つの関係するテーブルを結合し、現行の AR インスタンスのプライマリキーでフィルタリングするものです。 そして、クエリの結果は、関連する AR の (単一または複数の) インスタンスとして、プロパティに保存されます。 これは レイジーローディング (Lazy Loading) アプローチとして知られており、リレーショナルクエリは関連するオブジェクトが最初にアクセスされて初めて実行されます。 以下の例は実際にこのアプローチをどのように使用するかを示します。
// ID が 10 である記事を取得
$post=Post::model()->findByPk(10);
// 記事の著者を取得。リレーショナルクエリはここで実行される
$author=$post->author;
情報: リレーションにより関連したインスタンスが取得できない場合、対応するプロパティは null または空の配列となります。
BELONGS_TO
とHAS_ONE
リレーションの場合結果は null です。HAS_MANY
とMANY_MANY
では空の配列です。HAS_MANY
とMANY_MANY
リレーションは、オブジェクトの配列を返すため、個々のプロパティにアクセスする前に、結果の配列をループする必要があることに注意してください。 そうでなければ、"Trying to get property of non-object (非オブジェクトのプロパティを取得しようとしている)" というエラーが発生します。
レイジーローディングアプローチは使うのに非常に便利ですが、それはいくつかの場合に効率的ではありません。
例えば N
個の著者情報にアクセスする場合、レイジーローディングアプローチを使うと N
個のジョインクエリを発行しなければなりません。
この状況ではいわゆる イーガーローディング (Eager Loading) アプローチをとる必要があります。
イーガーローディングアプローチでは、主となる AR インスタンスと共に関連する AR インスタンスを取得します。 これは、AR において find か findAll のいずれかと共に with() メソッドを用いることで達成されます。 例えば、
$posts=Post::model()->with('author')->findAll();
上記のコードは Post
インスタンスの配列を返します。
レイジーアプローチとは異なり、author
プロパティにアクセスする前に、各々の Post
インスタンスの author
プロパティは関連した User
インスタンスを格納しています。
記事ごとにジョインクエリを実行する代わりに、イーガーローディングアプローチでは、一回のジョインクエリによって、すべての記事を著者と共に取得します。
複数のリレーション名を with() メソッド中で指定することができ、イーガーローディングアプローチでは一度で全ての情報を取得できます。 例えば、以下のコードは、著者とカテゴリーを付加して、すべての記事を取得します。
$posts=Post::model()->with('author','categories')->findAll();
イーガーローディングを入れ子で実行することもできます。 リレーション名のリストの代わりに、以下のようにリレーション名の階層的な表現を with() メソッドに渡します。
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();
上記の例は、著者とカテゴリーと共にすべての記事を取得します。 さらに各々の著者のプロフィールと記事を取得します。
イーガーローディングは、下記のように、CDbCriteria::with プロパティを指定しても実行することが出来ます。
$criteria=new CDbCriteria;
$criteria->with=array(
'author.profile',
'author.posts',
'categories',
);
$posts=Post::model()->findAll($criteria);
または
$posts=Post::model()->findAll(array(
'with'=>array(
'author.profile',
'author.posts',
'categories',
)
));
場合によっては、リレーショナルクエリを実行する必要があるけれども、関連するモデルは取得したくない、ということがあります。
たとえば、数多くの Post
を投稿した User
が沢山いるとしましょう。
記事は公開されている場合もあれば、下書き状態にとどまっている場合もあります。
これは Post
モデルの published
フィールドによって決定されます。
そして、公開されている記事を持っているユーザをすべて取得したいけれども、記事そのものには関心がない、という場合です。
これは以下のようにして達成することが出来ます。
$users=User::model()->with(array(
'posts'=>array(
// 記事は SELECT したくない
'select'=>false,
// けれども、公開されている記事を持つユーザだけを取得したい
'joinType'=>'INNER JOIN',
'condition'=>'posts.published=1',
),
))->findAll();
既に述べたように、リレーションの宣言において追加のオプションを指定することが出来ます。 これらのオプションは、"名前-値" のペアとして指定され、リレーショナルクエリをカスタマイズするのに用いられます。 それらの概要は以下の通りです。
select
: 関連するARクラスのために選ばれるカラムのリスト。
デフォルトは '*' でありすべてのカラムを意味します。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
condition
: WHERE
句です。
デフォルトは空で無条件を意味します。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
params
: 生成された SQL 文にバインドされるパラメータ。
これは "名前-値" のペアの配列として与えられなければなりません。
on
: ON
句です。
ここで指定される条件は、AND
オペレータ を使用して、JOIN の条件に追加されます。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
このオプションは MANY_MANY
リレーションには適用されません。
order
: ORDER BY
句です。
デフォルトでは空で無条件を意味します。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
with
: このオブジェクトと共にロードすべき、子供のリレーションオブジェクトのリストです。
このオプションを不適切に使用すると、無限リレーションループが形成される可能性がありますので、注意してください。
joinType
: このリレーションのジョインタイプで、デフォルトでは LEFT OUTER JOIN
です。
alias
: このリレーションと関連付けられたテーブルのエイリアスです。
デフォルトは null で、テーブルのエイリアスはリレーション名と同じであることを意味します。
together
: このリレーションと関連付けられたテーブルが、主テーブルおよびその他のテーブルとの結合を強制されるかどうかを決定します。
このオプションは HAS_MANY
および MANY_MANY
のリレーションでのみ意味があります。
このオプションが false にセットされた場合は、HAS_MANY
または MANY_MANY
のリレーションに関連付けられたテーブルは、メインの SQL クエリとは分離された SQL クエリの中で主テーブルと結合されます。
これは、そうする方が、重複して返されるデータが少なくなり、全体としてのクエリのパフォーマンスを向上させることが出来るからです。
このオプションが true にセットされた場合は、関連付けられたテーブルは常に、主テーブルがページ分割されても、単一の SQL クエリの中で主テーブルと結合されます。
そして、このオプションが何もセットされない場合は、主テーブルがページ分割されない場合に限って、関連付けられたテーブルが単一の SQL クエリの中で主テーブルと結合されます。
更なる詳細については、"リレーショナルクエリのパフォーマンス" を参照して下さい。
join
: 追加のJOIN
句です。
デフォルトでは空です。
このオプションは、バージョン 1.1.3 以降で利用可能です。
joinOptions
: USE INDEX
のような JOIN
後の操作を設定するためのプロパティです。
文字列型の値を HAS_MANY
と MANY_MANY
のリレーションの JOIN
のために使うことが出来ます。
一方、配列型の値は MANY_MANY
リレーションのためだけに使われることを想定しています。
配列の最初の要素がジャンクションテーブルの JOIN
のために使われ、2番目の要素がターゲットテーブルの
JOIN
のために使われます。
このオプションは、バージョン 1.1.15 以降で利用可能です。
group
: GROUP BY
句です。デフォルトは空です。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
having
: HAVING
句です。デフォルトは空です。
このオプションで参照されるカラム名は曖昧さを無くさなければなりません。
index
: 関連するオブジェクトを格納する配列のキーとして使われる値を持っているカラムの名前です。
このオプションを設定しない場合は、関連するオブジェクトの配列は 0 から始まる整数のインデックスを持ちます。
このオプションは、HAS_MANY
および MANY_MANY
のリレーションに対してのみ設定出来ます。
scopes
: 適用するスコープです。
単一のスコープの場合は 'scopes'=>'scopeName'
のように指定し、複数のスコープの場合は 'scopes'=>array('scopeName1','scopeName2')
のように指定する事が出来ます。
このオプションは、バージョン 1.1.9 以降で利用可能です。
さらに、以下のオプションは、レイジーローディングの際に、特定のリレーションのために利用できます:
limit
: 選択される行数の制限。
このオプションは BELONGS_TO
リレーションには適用されません。
offset
: 選択される行のオフセット。
このオプションは BELONGS_TO
リレーションには適用されません。
through
: 関連するデータを取得する際に、ブリッジとして使用されるモデルのリレーションの名前。
このオプションは、バージョン 1.1.7 以降で、HAS_MANY
および MANY_MANY
のリレーションに対して
使用可能です。1.1.14 以降は、BELONGS_TO
に対しても使用出来ます。
以下では、User
における posts
リレーション宣言を修正して、上記のオプションのいくつかを含むようにしてみましょう。
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
'order'=>'posts.create_time DESC',
'with'=>'categories'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}
このように宣言した場合、$author->posts
としてアクセスすると、著者の記事を作成日時の降順でソートして取得することが出来ます。
また、各記事のインスタンスには、そのカテゴリも付加されます。
注意: イーガーローディングを使う場合、'order'、'group'、'having'、'limit' および 'offset' というようなオプションは無視されます。このようなパラメータを適用したいときは、 メインモデルのクライテリアのレベルで設定しなければなりません。
結合される二つ以上のテーブルに同じ名前のカラムが出現する場合、カラム名の曖昧さを無くする必要があります。 これは、カラム名の前にテーブルのエイリアス(別名)を追加することで行います。
リレーショナル AR クエリにおいては、主テーブルのエイリアスは t
に固定されます。
一方、関連するテーブルのエイリアスは、デフォルトでは、対応するリレーション名と同じものになります。
例えば、次の文では、Post
および Comment
のエイリアスは、それぞれ、t
および comments
です。
$posts=Post::model()->with('comments')->findAll();
今、仮に、Post
も Comment
も、それぞれ、記事またはコメントの作成日時を示す create_time
というカラムを持っているとします。
そして、記事とそれに対するコメントを一緒にして取得するときに、先ず記事の作成日時でソートし、次にコメントの作成日時でソートしたいとします。
このとき、create_time
というカラム名の曖昧さを無くすために次のようにします。
$posts=Post::model()->with('comments')->findAll(array(
'order'=>'t.create_time, comments.create_time'
));
ヒント: 関連するテーブルのデフォルトのエイリアスはリレーション名です。リレーションを別のリレーションの中から参照する 場合でも、そのテーブルのエイリアスは前者のリレーション名であり、親のリレーション名を前置したものにはならないということに 注意して下さい。例えば、'author.group' というリレーションのテーブルエイリアスは 'group' であり、'author.group' ではありません。
$posts=Post::model()->with('author', 'author.group')->findAll(array( 'order'=>'group.name, author.name, t.title' ));
テーブルエイリアスの衝突を回避するために、リレーションの alias プロパティを設定することが出来ます。
$comments=Comment::model()->with(array( 'author', 'post', 'post.author'=>array('alias'=>'p_author')))->findAll(array( 'order'=>'author.name, p_author.name, post.title' ));
with() と with
オプションの両方の場合とも、動的なリレーショナルクエリのオプションを使用することができます。
動的なオプションは、relations() メソッドの中で指定された既存のオプションを上書きします。
たとえば、上記の User
モデルにおいて、イーガーローディングのアプローチを使って、ある著者に属する記事を 昇順 (リレーション定義中の order
オプションは降順です) で取得したいならば、以下のように行います。
User::model()->with(array(
'posts'=>array('order'=>'posts.create_time ASC'),
'profile',
))->findAll();
動的なクエリオプションは、レイジーローディングのアプローチを使ってリレーショナルクエリを実行するときにも、使用できます。
そうするためには、リレーション名と同じ名前のメソッドを、パラメータに動的なクエリオプションを指定して、呼び出します。
例えば、下記のコードは、status
が 1 であるユーザの記事を返します:
$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));
上述のように、イーガーローディングアプローチは、主として、多数の関連オブジェクトにアクセスする必要があるシナリオで用いられます。 これは全ての必要なテーブルを結合して、長大で複雑な SQL 文を生成します。 長大な SQL 文は、たいていの場合は、望ましいものです。 なぜなら、関連するテーブルのカラムをもとにして、フィルタリングを単純化できるからです。しかし、効率的でない場合もいくつかあります。
例として、最近の記事とそれに対するコメントを一緒に取得したい場合を考えてみて下さい。 各記事が 10 個のコメント持っていると仮定すると、単一の長大な SQL 文を使った場合は、多数の冗長な記事データが返ってくることになります。 なぜなら、すべてのコメントに対して、それの元になった記事が繰り返されるからです。 次に、別のアプローチを試してみましょう。 最初に最近の記事に対するクエリを行い、次に記事に対するクエリを行います。 この新しいアプローチでは、二つの SQL 文を実行しなければなりません。利点は、クエリの結果に冗長性が無いことです。
では、どちらのアプローチがより効率的なのでしょうか。絶対的な答えはありません。 単一の長大な SQL 文を実行する方が効率的なこともあります。何故なら、SQL 文の解釈と実行をするのに、DBMS におけるオーバーヘッドを少なく出来るからです。 その一方、単一の SQL 文を使うと、冗長なデータが増えて、その結果、データの読み出しと処理に時間がかかるようになります。
こういう理由から、Yii は必要に応じて二つのアプローチを選択できるように、together
というクエリオプションを提供しています。
デフォルトでは、主たるモデルに対して LIMIT
が適用されない限り、単一の SQL 文を生成して、イーガーローディングを実行します。
リレーションの宣言において together
オプションを true に設定すれば、LIMIT
が使用される場合であっても、単一の SQL 文を生成するように強制することが可能です。
そして、together
オプションを false に設定すれば、いくつかのテーブルを別の SQL 文の中で結合するように設定することが出来ます。
例えば、最近の記事とそれに対するコメントを取得するのに、第二のアプローチを採用したい場合は、次のように Post
クラスの comments
リレーションを宣言します。
public function relations()
{
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
);
}
イーガーローディングを実行する場合にこのオプションを動的に設定することも可能です。
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();
上述のリレーショナルなクエリの他に、Yii はいわゆる統計クエリ (または集計クエリ) もサポートします。
これは、関連するオブジェクトに関する集計的な情報、例えば各々の記事に対するコメントの数や、各々の製品の平均点数などを検索するものです。
統計クエリを実行出来るのは、HAS_MANY
(例えば記事は多くのコメントを持つ) または MANY_MANY
(例えば記事は多くのカテゴリーに属し、カテゴリーは多くの記事を持つ) のリレーションを持つオブジェクトに対してのみです。
統計クエリを実行することは、既に解説したリレーショナルクエリを実行することと非常に類似しています。 リレーショナルクエリで行うように、最初に統計クエリを relations() 中で宣言する必要があります。
class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
);
}
}
上記において、我々は二つの統計クエリを宣言しています。commentCount
は記事に属しているコメントの数を計算します。
categoryCount
は記事が属しているカテゴリーの数を計算します。
Post
と Category
の関係が MANY_MANY
(結合テーブル post_category
を介して)であるのに対し、Post
と Comment
の関係が HAS_MANY
である点に注意してください。
このように、統計クエリの宣言は以前の節で解説したリレーション宣言と非常に類似しています。
唯一の違いはリレーションタイプが STAT
であるということです。
上記の宣言を用いて、$post->commentCount
という式で記事に対するコメントの数を取り出すことができます。
初めてこのプロパティにアクセスするとき、対応する結果を取り出すために暗黙のうちに SQL 文が実行されます。
すでに知っているように、これはいわゆる レイジーローディング アプローチです。
複数の記事についてコメント数を決定する必要があるならば、我々は イーガーローディング アプローチを使用することもできます。
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
上記の文は、すべての記事に対するコメント数とカテゴリー数を取り出すために、3つの SQL 文を実行します。
レイジーローディングアプローチを使う場合、N
個のポストがあるならば 2*N+1
の SQL クエリを必要とします。
デフォルトでは、統計クエリは、COUNT
式 (従って上記の例ではコメント数とカテゴリー数) を計算します。
これは、relations() で宣言するときに、追加のオプションを指定することでカスタマイズ可能です。
利用できるオプションは、下の通りまとめられます。
select
: 統計表現。デフォルトでは COUNT(*)
であり、子オブジェクトの数を意味します。
defaultValue
: 統計クエリの結果を受けないレコードに割り当てられる値。
たとえば記事がコメントを持たないならば、その commentCount
はこの値を取るでしょう。
このオプションのデフォルト値は 0 です。
condition
: WHERE
句です。デフォルト値は空です。
params
: 生成された SQL 文にバインドされるパラメータ値。これは "名前-値" のペアの配列として与えます。
order
: ORDER BY
句です。デフォルト値は空です。
group
: GROUP BY
句です。デフォルト値は空です。
having
: HAVING
句です。デフォルト値は空です。
リレーショナルクエリは 名前付きスコープ と組み合わせて実行できます。 リレーショナルクエリは、二つの方法で利用できます。 一つ目は、名前付きスコープをメインモデルに適用させる方法、二つ目は、名前付きスコープをリレーションモデルに適用させる方法です。
下記のコードは、メインモデルに名前付きスコープを適用する方法を示します。
$posts=Post::model()->published()->recently()->with('comments')->findAll();
これは、リレーショナルでないクエリにとても似ています。
唯一の違いは、名前付きスコープのチェーンの後で with()
をコールする点です。
このクエリは、最近公開された記事とそれらのコメントを返します。
また、下記のコードは、リレーションモデルに名前付きスコープを適用する方法を示します。
$posts=Post::model()->with('comments:recently:approved')->findAll();
// 1.1.7 以降の場合、または
$posts=Post::model()->with(array(
'comments'=>array(
'scopes'=>array('recently','approved')
),
))->findAll();
// 1.1.7 以降の場合、または
$posts=Post::model()->findAll(array(
'with'=>array(
'comments'=>array(
'scopes'=>array('recently','approved')
),
),
));
上記のクエリは、全ての記事とそれらの承認済みコメントを返します。
comments
はリレーション名を、recently
と approved
は Comment
モデルクラスで宣言された二つの名前付きスコープを示している事に注意してください。
リレーション名と名前付きスコープはコロンで区切ります。
時によっては、名前付きスコープを適用したリレーションを、上記で示されているイーガーローディングの方法ではなく、レイジーローディングの技法を使って取得する必要があるかも知れません。 その場合には、下記の文法によって目的を達することが出来ます。
~~ [php] // リレーション名の繰り返しに注目して下さい。これは必要です。 $approvedComments = $post->comments('comments:approved'); ~~
また、名前付きスコープは CActiveRecord::relations() で宣言されたリレーションルールの with
オプションの中で指定することもできます。
以下の例で、$user->posts
にアクセスすると、その記事の全ての 承認された (approved) コメントが返されます。
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
'with'=>'comments:approved'),
);
}
}
// 1.1.7 以降の場合、または
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
'with'=>array(
'comments'=>array(
'scopes'=>'approved'
),
),
),
);
}
}
注意: 1.1.7 より前では、関連したモデルに適用される名前付きスコープは、CActiveRecord::scopes で定義しなければなりません。結果的に、それらをパラメータ化することはできません。
バージョン 1.1.7 以降、リレーショナルクエリの名前付きスコープにパラメータを渡すことが可能になりました。
例えば、Post
に rated
という名前のスコープがあって、レーティングの下限を受け付ける場合、User
から次のようにして使うことが出来ます。
$users=User::model()->findAll(array(
'with'=>array(
'posts'=>array(
'scopes'=>array(
'rated'=>5,
),
),
),
));
class Post extends CActiveRecord
{
......
public function rated($rating)
{
$this->getDbCriteria()->mergeWith(array(
'condition'=>'rating=:rating',
'params'=>array(':rating'=>$rating),
));
return $this;
}
......
}
through
を使用する場合、リレーションの定義は次のようにしなければなりません。
'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'),
上記の array('key1'=>'key2')
において、
key1
は through
で指定されているリレーション (この場合はposts
) で定義されているキーであり、key2
は リレーションが指し示すモデル (この場合はComment
) で定義されているキーです。through
は HAS_ONE
, BELONGS_TO
および HAS_MANY
のリレーションで使用出来ます。
HAS_MANY through ER
through
を使う HAS_MANY
の一例として、ユーザがロールを通じてグループに割り当てられている場合に、特定のグループに属するユーザを取得することを挙げることが出来ます。
もう少し複雑な例としては、特定のグループに属する全てのユーザに対する全てのコメントを取得することがそれに当ります。
この場合は、単一のモデルにいくつかの through
を使うリレーションを使用する必要があります。
class Group extends CActiveRecord
{
...
public function relations()
{
return array(
'roles'=>array(self::HAS_MANY,'Role','group_id'),
'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'),
'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'),
);
}
}
// 一致する全ユーザとともに、全グループを取得
$groups=Group::model()->with('users')->findAll();
// 一致する全てのユーザとロールとともに、全グループを取得
$groups=Group::model()->with('roles','users')->findAll();
// グループ ID が 1 である全てのユーザとロールを取得
$group=Group::model()->findByPk(1);
$users=$group->users;
$roles=$group->roles;
// グループ ID が 1 である全てのコメントを取得
$group=Group::model()->findByPk(1);
$comments=$group->comments;
HAS_ONE through ER
through
を使う HAS_ONE
の使用例としては、ユーザがプロファイルを使って住所と結び付けられている場合に、ユーザの住所を取得することを挙げることが出来ます。
これらのエンティティ (ユーザ、プロファイル、住所) は全て対応するモデルを持っています。
class User extends CActiveRecord
{
...
public function relations()
{
return array(
'profile'=>array(self::HAS_ONE,'Profile','user_id'),
'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'),
);
}
}
// ID が 1 であるユーザの住所を取得する
$user=User::model()->findByPk(1);
$address=$user->address;
through
は、ブリッジモデルを使って自分自身へと結び付けられるモデルにも使用することが出来ます。
以下の例では、他のユーザを指導するユーザがそれです。
through self ER
この例における関係は、次のように定義することが出来ます。
class User extends CActiveRecord
{
...
public function relations()
{
return array(
'mentorships'=>array(self::HAS_MANY,'Mentorship','teacher_id','joinType'=>'INNER JOIN'),
'students'=>array(self::HAS_MANY,'User',array('student_id'=>'id'),'through'=>'mentorships','joinType'=>'INNER JOIN'),
);
}
}
// ID が 1 である先生に教えられている全ての生徒を取得する
$teacher=User::model()->findByPk(1);
$students=$teacher->students;
バージョン 1.1.15 以降は、JOIN 後の操作を追加して設定することが出来ます。
CBaseActiveRelation::$joinOptions
が追加されました。
下記のようなモデルとリレーションがあるとします。
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts' => array(self::HAS_MANY, 'Post', 'user_id'),
);
}
}
class Post extends CActiveRecord
{
public function relations()
{
return array(
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
'tags' => array(self::MANY_MANY, 'Tag', '{{post_tag}}(post_id, tag_id)'),
);
}
}
class Tag extends CActiveRecord
{
public function relations()
{
return array(
'posts' => array(self::MANY_MANY, 'Post', '{{post_tag}}(tag_id, post_id)'),
);
}
}
USE INDEX
句を使ったクエリコードのサンプルを示します。
$users=User::model()->findAll(array(
'select'=>'t.id,t.name',
'with'=>array(
'posts'=>array(
'alias'=>'p',
'select'=>'p.id,p.title',
'joinOptions'=>'USE INDEX(post__user)',
),
),
));
$posts=Post::model()->findAll(array(
'select'=>'t.id,t.title',
'with'=>array(
'tags'=>array(
'alias'=>'a',
'select'=>'a.id,a.name',
'joinOptions'=>'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)',
),
),
));
$posts=Post::model()->findAll(array(
'select'=>'t.id,t.title',
'with'=>array(
'tags'=>array(
'alias'=>'a',
'select'=>'a.id,a.name',
'joinOptions'=>array(
'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)',
'USE INDEX(tag__name)',
),
),
),
));
上記のコードは、それぞれ以下のような MySQL クエリを生成します。
SELECT `t`.`id` AS `t0_c0`, `t`.`name` AS `t0_c1`, `p`.`id` AS `t1_c0`, `p`.`title` AS `t1_c2` FROM `tbl_user` `t` LEFT OUTER JOIN `tbl_post` `p` USE INDEX(post__user) ON (`p`.`user_id`=`t`.`id`); SELECT `t`.`id` AS `t0_c0`, `t`.`title` AS `t0_c2`, `a`.`id` AS `t1_c0`, `a`.`name` AS `t1_c1` FROM `tbl_post` `t` LEFT OUTER JOIN `tbl_post_tag` `tags_a` USE INDEX(post_tag__tag) USE INDEX(post_tag__post) ON (`t`.`id`=`tags_a`.`post_id`) LEFT OUTER JOIN `tbl_tag` `a` ON (`a`.`id`=`tags_a`.`tag_id`); SELECT `t`.`id` AS `t0_c0`, `t`.`title` AS `t0_c2`, `a`.`id` AS `t1_c0`, `a`.`name` AS `t1_c1` FROM `tbl_post` `t` LEFT OUTER JOIN `tbl_post_tag` `tags_a` USE INDEX(post_tag__tag) USE INDEX(post_tag__post) ON (`t`.`id`=`tags_a`.`post_id`) LEFT OUTER JOIN `tbl_tag` `a` USE INDEX(tag__name) ON (`a`.`id`=`tags_a`.`tag_id`);
クエリオプション $joinOptions
は、次に示すように、リレーションの宣言の中で設定することも出来ます。
class Post extends CActiveRecord
{
public function relations()
{
return array(
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
'tags' => array(self::MANY_MANY, 'Tag', '{{post_tag}}(post_id, tag_id)',
'joinOptions' => array(
'USE INDEX(post_tag__tag) USE INDEX(post_tag__post)',
'USE INDEX(tag__name)',
),
),
);
}
}
Found a typo or you think this page needs improvement?
Edit it on github !
Signup or Login in order to comment.