2011-10-26 11 views
0

CakePHPのドキュメントのほとんどは、具体的な関係の結果に基づいてフィルタリングする方法を教えているようです。私が見つけることができないのは、データを返さないリレーションシップを持つ結果をフィルタリングする方法です。CakePHPでHABTM関係の空の結果を除外

たとえば、投稿とタグを持つ典型的なブログの例を考えてみましょう。タグは多くの投稿(HABTM)に属しています。この議論については、次の表の構造を前提としています

posts (id, title) 
tags (id, name) 
posts_tags (post_id, tag_id) 

はどのように(すなわち、何の投稿を返さないだろうタグを除外)、それらに関連する1つまたは複数の記事を持っている唯一のタグを見つけるのですか?

理想的な結果セットには、(書式設定のための追加引用符)のようになります。

Array (
    [0] => Array (
      [Tag] => Array (
         [id] => 1 
         [name] => 'Tag1') 
      [Post] => Array (
         [0] => Array (
           [id] => 1 
           [title] => 'Post1') 
         [1] => Array (
           [id] => 4 
           [title] => 'Post4')) 
    ) 
    [1] => Array (
      [Tag] => Array (
         [id] => 4 
         [name] => 'Tag5') 
      [Post] => Array (
         [0] => Array (
           [id] => 4 
           [title] => 'Post4') 
         [1] => Array (
           [id] => 5 
           [title] => 'Post5') 
         [2] => Array (
           [id] => 6 
           [title] => 'Post6')) 
    )) 
+0

[この投稿](http://www.volved.com/index.php/2009/03/26/cakephp-12-group-by-with-having-count-habtm-associations-limit-the-関連するデータベースのクエリ)はこの問題に対処しているようです。しかし、1.3。 – Ross

答えて

1

私が今まで信頼性の高い方法でこれを行うために見つけた唯一の方法はad hoc joinsを使用することです。これらを使用して、内部結合タイプを指定し、必要なものを正確に取得することができます。

+0

多分私は何かを見逃していましたが、リンクの構文を試した後、タグには投稿がありますが、タグは複数回表示されます(関連付けられたPostごとに1回)。また、投稿のタイトルはリンクされていません。 –

+1

'DISTINCT()'を使ってみてください。 –

0

以下は、ケーキ1.3で試験した。

class Post extends AppModel { 
    var $hasAndBelongsToMany = 'Tag'; 
} 

class Tag extends AppModel { 
    var $hasAndBelongsToMany = 'Post'; 
} 

をケーキの独自のドキュメントによると::

がしたい、またはすでに実行し、これが正常に適用される他のすべての状況のた​​めのモデルで定義されたHABTM関係を持っている、おそらくあなたを開始するには、[ 1]

いくつかの団体(belongsToのとhasOneのは)自動実行CakePHPでは

あなたは、関連する1のデータに基づいてモデル を取得するためにクエリを発行することができますので、データを取得するために結合します。

ただし、これはhasManyとhasAndBelongsToManyの関連付けでは当てはまりません。ここに があります。ここで強制的にジョインするのがレスキューです。必要な結合を定義するだけで、テーブルを結合してクエリに望ましい結果を得るのに、 が必要です。

空のHABTM結果を除外するのは、これらの時間のうちの1つです。ケーキブックのこの同じセクションでは、これを達成する方法について説明していますが、結果が得られたテキストを読んであまりにも明白ではありませんでした。 Cake Bookの例では、タグ - >投稿タグ - >投稿の代わりに、ブック - >ブックタグ - >タグの\ joinパスを使用しています。この例では、TagControllerから次のように設定します。

$options['joins'] = array(
    array(
     'table'  => 'posts_tags', 
     'alias'  => 'PostsTag', 
     'type'  => 'INNER', 
     'foreignKey' => false, 
     'conditions' => 'PostsTag.tag_id = Tag.id' 
    ), 
    array(
     'table'  => 'posts', 
     'alias'  => 'Post', 
     'type'  => 'INNER', 
     'foreignKey' => false, 
     'conditions' => 'Post.id = PostsTag.post_id' 
    ) 
); 

$tagsWithPosts = $this->Tag->find('all', $options); 

foreignKeyをfalseに設定してください。これはCakeに、結合条件を把握せず、代わりに指定した条件だけを使用するようにすべきであることを伝えます。

これは、ジョインの性質上、重複した行を戻すのが一般的です。返されるSQLを減らすには、必要に応じてフィールドにDISTINCTを使用します。すべてのフィールドが通常find( 'all')によって返されるようにする場合は、各列をハードコードするために必要な複雑さが加わります。 (あなたのテーブル構造はそれを頻繁に変えるべきではありませんが、それが起こる可能性があります。プログラムですべての列をつかむために、findメソッド呼び出しの前に、以下を追加します。

$options['fields'] = array('DISTINCT Tag.' 
        . implode(', Tag.', array_keys($this->Tag->_schema))); 
// **See note 

HABTM関係が AFTER主な選択を実行していることに注意することが重要です。本質的に、Cakeは適格なタグのリストを取得し、関連するPostを得るためにSELECTステートメントの別のラウンドを実行します。これはSQLのダンプから見ることができます。手動で設定した「結合」は最初の選択に適用され、希望するタグのセットが与えられます。その後、組み込みのHABTMが再び実行され、関連するすべてのPostがそれらのタグに与えられます。私たちは目標である投稿を持たないタグは持っていませんが、追加された場合、私たちは当初の条件の一部ではないタグに関連付けられた投稿を得ることができます。

$options['conditions'] = 'Post.id = 1'; 

は、次のような結果が得られます:以下の条件の追加例えば

、問題のサンプルデータに基づいて

Array (
    [0] => Array (
      [Tag] => Array (
         [id] => 1 
         [name] => 'Tag1') 
      [Post] => Array (
         [0] => Array (
           [id] => 1 
           [title] => 'Post1') 
         [1] => Array (
           [id] => 4 
           [title] => 'Post4')) 
    ) 
) 

は、唯一のタグ1は、当社の「条件」と関連していましたステートメント。したがって、これは '結合'によって返された唯一の結果でした。しかし、HABTMはこの後に実行されたので、Tag1に関連付けられたすべての投稿(Post1とPost4)を取得しました。

明示的な結合を使用して目的の初期データセットを取得するこの方法も、Quick Tip - Doing Ad-hoc Joins in Model::find()で説明しています。また、このテクニックを一般化して、それをAppModelに追加する方法を示します(find()を拡張)。

私たちは本当にだけで、我々は 'を含む' [2]オプション句を追加する必要があり、同様POST1を見たい場合は、次の結果を与える

$this->Tag->Behaviors->attach('Containable'); 
$options['contain'] = 'Post.id = 1'; 

Array (
    [0] => Array (
      [Tag] => Array (
         [id] => 1 
         [name] => 'Tag1') 
      [Post] => Array (
         [0] => Array (
           [id] => 1 
           [title] => 'Post1')) 
    ) 
) 

代わりのをContainableを使用すると、bindModelを使用して、find()のこのインスタンスでHABTM関係を再定義できます。私は初心者がケーキのオートマジック能力の周りに頭をラップしようとしているため、明示的なジョイン作ることは(私はそれがあった知って見ると理解しやすいと感じる

$this->Tag->bindModel(array(
    'hasAndBelongsToMany' => array(
     'Post' => array('conditions' => 'Post.id = 1')) 
    ) 
); 

:bindModelでは、目的のポストの条件を追加します私のために)。これを行うもう1つの有効かつ間違いなくもっと 'Cake'の方法は、unbindModelとbindModelを排他的に使用することです。 http://nuts-and-bolts-of-cakephp.comのTeknoidには、これを行う方法に関する良い書き方があります:http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/。さらに、teknoidはこれをgithubから得ることができる行動にしました:http://nuts-and-bolts-of-cakephp.com/2009/09/26/habtamable-behavior/

**これは、データベースで定義された順序で列を引き出します。したがって、主キーが最初に定義されていない場合は、DISTINCTが期待どおりに適用されないことがあります。 array_diff_keyを使用して$ this-> Model-> primaryKeyのプライマリキーを除外するには、これを変更する必要があります。