リレーショナルアクティブレコード

単一のデータベーステーブルからデータを選択するためにアクティブレコード(AR)を使う方法を見てきました。 この章では、ARを使って、いくつかの関係するデータベーステーブルをつなげ、結合されたデータセットを取得する方法を示します。

リレーショナルARを使う場合は、結合すべきテーブルに主キー・外部キー制約が宣言されていることが推奨されます。 この制約がリレーショナルデータの一貫性と整合性を保持するために役立ちます。

分りやすくするために、この章では例題として、以下のエンティティ関係(ER)図に示されるデータベーススキーマを使用します。

ER Diagram ER図

ER Diagram ER図

情報: 外部キー制約のサポートはDBMS毎に異ります。 SQLite (3.6.19 未満) は外部キー制約をサポートしませんが、テーブルを作成する際に制約を宣言することが出来ます。 MySQL の MyISAM エンジンは外部キーを全くサポートしません。

1. リレーションの宣言

ARのリレーショナルクエリを使用する前に、ARに対して他のARクラスとどのように関係しているかを知らせる必要があります。

2つのARクラスのリレーションは、ARクラスによって表現されるデータベーステーブルのリレーションと直接関係しています。 データベースの観点からは、2つのテーブルAとBの関係には、3つのタイプがあります。 1対多(例えばtb_usertbl_post)、1対1(例えばtbl_usertbl_profile)、多対多(例えばtbl_categorytbl_post)。 ARでは、以下の4種類のリレーションがあります。

  • BELONGS_TO: テーブルAとBの関係が1対多ならば、BはAに属しています(e.g. Post blongs to User)。

  • HAS_MANY: 同じくテーブルAとBの関係が1対多ならば、Aは多くのBを持っています(e.g. User has many Post)。

  • HAS_ONE: これはAがたかだか1つのBを持っているHAS_MANYの特例です(e.g. User has at most one Profile)。

  • MANY_MANY: これはデータベースにおいて多対多の関係と対応します。 多対多の関係を1対多の関係に分割するために、関連付け用のテーブルが必要になります。なぜなら 大部分のDBMSは、直接多対多の関係をサポートしないためです。 例題のデータベーススキーマでは、tbl_post_category がこの目的のために使用されます。 AR用語では、BELONGS_TOHAS_MANYの組合せとして、MANY_MANYを説明することができます。 例えばPostは多くのCategoryに属しています。そしてCategoryには多くのPostがあります。

ARでのリレーション宣言は、CActiveRecordクラスのrelations()メソッドをオーバライドすることで行います。 このメソッドはリレーション構成の配列を返します。 各々の配列要素は以下のフォーマットで示す一つのリレーションを意味します。

'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...付加オプション)

ここでVarNameはリレーションの名前です。RelationTypeはリレーションのタイプを指定します。 そしてそれは4つの定数、self::BELONGS_TOself::HAS_ONEself::HAS_MANYself::MANY_MANYのうちの1つです。 ClassNameはこのARクラスに関連付けられるARクラスの名前です。 ForeignKeyはリレーションに関係する外部キーを指定します。 そして付加オプションは各々をリレーションの終わりに指定することができます(後で説明します)。

以下のコードでどのようにUserPostクラスのリレーションを宣言するかを示します。

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インスタンス(単一または複数)で満されます。 例えば、$authorUser ARインスタンスを表している場合、$author->posts を使って、関連した Post インスタンスにアクセスすることが出来ます。

2. リレーショナルクエリの実行

リレーショナルクエリを実行する最も単純な方法は、ARインスタンスのリレーショナルなプロパティを読み出すことです。 プロパティが以前にアクセスされていない場合には、リレーショナルクエリが開始されます。 このクエリは2つの関係するテーブルを結合し、現行のARインスタンスの主キーでフィルタリングするものです。 そして、クエリの結果は、関連する AR クラスの(単一または複数の)インスタンスとして、プロパティに保存されます。 これはレイジーローディングアプローチとして知られており、リレーショナルクエリは関連するオブジェクトが最初にアクセスされて初めて実行されます。 以下の例は実際にこのアプローチをどのように使用するかを示します。

// ID番号が10の投稿を取得
$post=Post::model()->findByPk(10);
// 投稿の著者を取得。リレーショナルクエリはここで実行される
$author=$post->author;

情報: リレーションにより関連したインスタンスが取得できない場合、 対応するプロパティは null または空の配列となります。 BELONGS_TOHAS_ONEリレーションの場合結果は null です。 HAS_MANYMANY_MANYでは空の配列です。 HAS_MANYMANY_MANY リレーションは、オブジェクトの配列を返すため、 個々のプロパティにアクセスする前に、結果を通してループする必要があることに注意してください。 そうでなければ、「Trying to get property of non-object(非オブジェクトのプロパティを取得しようとしている)」エラーが発生します。

レイジーローディングアプローチは使うのに非常に便利ですが、それはいくつかの場合に効率的ではありません。 例えばN個の著者情報にアクセスする場合、レイジーローディングアプローチを使うとN個のジョインクエリを発行しなければなりません。 この状況ではいわゆるイーガーローディングアプローチをとる必要があります。

イーガーローディングアプローチでは、主なARインスタンスと共に関連するARインスタンスを取得します。 これは、ARにおいてfindfindAllのいずれかと共に with()メソッドを用いることで達成されます。例えば、

$posts=Post::model()->with('author')->findAll();

上記のコードはPostインスタンスの配列を返します。 レイジーアプローチとは異なり、プロパティにアクセスする前に、各々の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',
    )
));

3. 関連するモデルを取得しないリレーショナルクエリを実行する

場合によっては、リレーショナルクエリを実行する必要があるけれども、関連するモデルは取得したくない、ということがあります。 たとえば、数多くの Post を投稿した User が沢山いるとしましょう。 投稿記事は公開されている場合もあれば、下書き状態にとどまっている場合もあります。 これは Post モデルの published フィールドによって決定されます。 そして、公開されている投稿記事を持っているユーザをすべて取得したいけれども、投稿記事そのものには関心がない、という場合です。 これは以下のようにして達成することが出来ます。

$users=User::model()->with(array(
    'posts'=>array(
        // 投稿記事は SELECT したくない
        'select'=>false,
        // けれども、公開されている投稿記事を持つユーザだけを取得したい
        'joinType'=>'INNER JOIN',
        'condition'=>'posts.published=1',
    ),
))->findAll();

4. リレーショナルクエリのオプション

既に述べたように、リレーションの宣言において追加のオプションを指定することが出来ます。 これらのオプションは、名前-値のペアとして指定されますが、リレーショナルクエリをカスタマイズするのに用いられます。 それらの概要は以下の通りです。

  • 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 以降で利用可能です。

  • 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: 関連するデータを取得する際に、ブリッジとして使用されるモデルのリレーションの名前。 HAS_MANY および MANY_MANY のリレーションに対してのみ設定出来ます。 このオプションは、バージョン 1.1.7 以降で利用可能です。

以下では、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 としてアクセスすると、著者の投稿記事を作成日時の降順でソートして取得することが出来ます。 また、各投稿記事のインスタンスには、そのカテゴリも付加されます。

5. 列名の曖昧さを無くする

結合される二つ以上のテーブルに同じ名前の列が出現する場合、 列名の曖昧さを無くする必要があります。 これは、列名の前にテーブルの別名(エイリアス)を追加することで行います。

リレーショナル AR クエリにおいては、主テーブルの別名は t に固定されます。 一方、関連するテーブルの別名は、デフォルトでは、対応するリレーション名と同じものになります。 例えば、次の文では、Post および Comment の別名は、それぞれ、t および comments です。

$posts=Post::model()->with('comments')->findAll();

今、仮に、PostComment も、それぞれ、投稿記事またはコメントの作成日時を示す create_time という列を持っているとします。そして、投稿記事とそれに対するコメントを一緒にして取得するときに、先ず投稿記事の作成日時でソートし、次にコメントの作成日時でソートしたいとします。このとき、create_time という列名の曖昧さを無くすために次のようにします。

$posts=Post::model()->with('comments')->findAll(array(
    'order'=>'t.create_time, comments.create_time'
));

6. 動的リレーショナルクエリオプション

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'));

7. リレーショナルクエリのパフォーマンス

上述のように、イーガーローディングアプローチは、主として、多数の関連オブジェクトにアクセスする必要があるシナリオで用いられます。 これは全ての必要なテーブルを結合して、長大で複雑な SQL 文を生成します。 長大な SQL 文は、たいていの場合は、望ましいものです。 なぜなら、関連するテーブルの列をもとにして、フィルタリングを単純化できるからです。しかし、効率的でない場合もいくつかあります。

例として、最近の投稿記事とそれに対するコメントを一緒に取得したい場合を考えてみて下さい。 各投稿記事が 10 個のコメント持っていると仮定すると、単一の長大な SQL 文を使った場合は、多数の冗長な投稿記事データが返ってくることになります。 なぜなら、すべてのコメントに対して、それの元になった投稿記事が繰り返されるからです。 次に、別のアプローチを試してみましょう。最初に最近の投稿記事に対するクエリを行い、次に投稿記事に対するクエリを行います。 この新しいアプローチでは、二つの SQL 文を実行しなければなりません。利点は、クエリの結果に冗長性が無いことです。

では、どちらのアプローチがより効率的なのでしょうか。絶対的な答えはありません。 単一の長大な SQL 文を実行する方が効率的なこともあります。何故なら、SQL 文の解釈と実行をするのに、DBMS におけるオーバーヘッドを少なく出来るからです。 その一方、単一の SQL 文を使うと、冗長なデータが増えて、その結果、データの読み出しと処理に時間がかかるようになります。

こういう理由から、Yii は必要に応じて二つのアプローチを選択できるように、togeter というクエリオプションを提供しています。 デフォルトでは、主たるモデルに対して 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();

8. 統計クエリ

上述のリレーショナルなクエリの他に、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)'),
        );
    }
}

上記において、我々は2つの統計クエリを宣言しています。commentCount は投稿記事に属しているコメントの数を計算します。 categoryCount は投稿記事が属しているカテゴリーの数を計算します。 PostCategory の関係が MANY_MANY (結合テーブル post_category を介して)であるのに対し、 PostComment の関係が 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句です。デフォルト値は空です。

9. Named Scope を使用したリレーショナルクエリ

リレーショナルクエリは Named Scope と組み合わせて実行できます。 リレーショナルクエリは、2 つの方法で利用できます。 1つ目は、Named Scope をメインモデルに適用させる方法、2つ目は、Named Scope をリレーションモデルに適用させる方法です。

下記のコードは、メインモデルに Named Scope を適用する方法を示します。

$posts=Post::model()->published()->recently()->with('comments')->findAll();

これは、リレーショナルでないクエリにとても似ています。 唯一の違いは、Named Scope チェーンの後で with() をコールする点です。 このクエリは、最近公開された投稿記事とそれらのコメントを返します。

また、下記のコードは、リレーションモデルに Named Scope を適用する方法を示します。

$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 はリレーション名を、recentlyapprovedComment モデルクラスで宣言された 2 つの Named Scope を示している事に注意してください。 リレーション名と Named Scope はコロンで区切ります。

また、Named Scope は 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 より前では、関連したモデルに適用される Named Scope は、CActiveRecord::scopes で定義しなければなりません。結果的に、それらをパラメータ化することはできません。

バージョン 1.1.7 以降、リレーショナルクエリの Named Scope にパラメータを渡すことが可能になりました。 例えば、Postrated という名前のスコープがあって、レーティングの下限を受け付ける場合、User から次のようにして使うことが出来ます。

$users=User::model()->findAll(array(
    'with'=>array(
        'posts'=>array(
            'scopes'=>array(
                'rated'=>5,
            ),
        ),
    ),
));

10. through を使うリレーショナルクエリ

through を使用する場合、リレーションの定義は次のようにしなければなりません。

'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'),

上記の array('key1'=>'key2') において、

  • key1throughRelationName で定義されているキーであり、
  • key2ClassName で定義されているキーです。

throughHAS_ONEHAS_MANY の両方のリレーションで使用出来ます。

HAS_MANY の through

HAS_MANY through ER

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

HAS_ONE through ER

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 は、ブリッジモデルを使って自分自身へと結び付けられるモデルにも使用することが出来ます。以下の例では、他のユーザを指導するユーザがそれです。

through self ER

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;
$Id: database.arr.txt 3416 2011-10-13 18:18:13Z alexander.makarow $

Be the first person to leave a comment

Please to leave your comment.