0

大多数 CakePHP 文档似乎都告诉您如何根据具体的关系结果进行过滤。我似乎找不到的是如何过滤掉具有不返回数据的关系的结果。

例如,以具有帖子和标签的典型博客示例为例。标签拥有并属于许多帖子 (HABTM)。对于本次讨论,假设以下表结构:

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

您如何仅找到与它们关联的一个或多个帖子的标签(即排除不会返回任何帖子的标签)?

理想的结果集看起来像(为格式化添加了引号):

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

2 回答 2

1

我发现以可靠的方式做到这一点的唯一方法是使用ad hoc joins。使用这些,您可以指定内部连接类型并获得您想要的。

于 2011-10-26T11:06:22.783 回答
0

以下是用 Cake 1.3 测试的。

首先,您可能想要或已经在模型上为通常适用的所有其他情况定义 HABTM 关系:

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

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

根据 Cake 自己的文档:[ 1 ]

在 CakePHP 中,一些关联(belongsTo 和 hasOne)执行自动连接以检索数据,因此您可以发出查询以根据相关关联中的数据检索模型。

但是 hasMany 和 hasAndBelongsToMany 关联并非如此。这就是强制加入来拯救的地方。您只需定义必要的连接来组合表并获得查询所需的结果。

排除空的 HABTM 结果是这些时间之一。蛋糕书的同一部分解释了如何实现这一点,但我并没有从阅读文本中发现结果实现这一点过于明显。在 Cake Book 的示例中,他们使用 \ 连接路径 Book -> BooksTag -> Tags,而不是我们的 Tag -> PostsTag -> Posts。对于我们的示例,我们在 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 关系主选择之后运行。本质上,Cake 获取了符合条件的标签列表,然后运行另一轮 SELECT 语句来获取相关的帖子;您可以从 SQL 转储中看到这一点。我们手动设置的“连接”适用于第一个选择,为我们提供所需的标签集。然后内置的 HABTM 将再次运行,为我们提供与这些标签相关的所有帖子。我们不会有任何没有帖子的标签,这是我们的目标,但如果添加了不属于我们任何初始“条件”的标签,我们可能会获得与该标签相关联的帖子。

例如,添加以下条件:

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

根据问题中的样本数据,只有 Tag1 与我们的“条件”语句相关联。所以这是“加入”返回的唯一结果。然而,由于 HABTM 在此之后运行,它抓取了与 Tag1 关联的所有帖子(Post1 和 Post4)。

快速提示 - 在 Model::find() 中执行 Ad-hoc Joins 中也讨论了这种使用显式连接来获取所需初始数据集的方法。本文还展示了如何推广该技术并将其添加到扩展 find() 的 AppModel。

如果我们真的也只想查看 Post1,我们需要添加一个 'contain'[ 2 ] 选项子句:

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

您可以使用 bindModel 来重新定义与 find() 实例的 HABTM 关系,而不是使用 Containable。在 bindModel 中,您将添加所需的 Post 条件:

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

我觉得对于那些试图了解蛋糕的自动魔法能力的初学者来说,使显式连接更容易看到和理解(我知道这是给我的)。另一种有效且可以说更“蛋糕”的方法是专门使用 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 中过滤掉主键。

于 2011-10-27T04:14:45.770 回答