【问题标题】:CakePHP: few joins, belongsTo and hasMany relations done in two queriesCakePHP:在两个查询中完成的很少连接、belongsTo 和 hasMany 关系
【发布时间】:2012-12-17 19:51:28
【问题描述】:

我需要一些关于 CakePHP 2.2.3 的帮助。

我有什么

我目前有以下设置:

Post hasMany Attachment

它工作正常,页面由 2 个查询生成:

SELECT *, `Post`.`id` 
FROM `posts` AS `Post` 
WHERE 1 = 1 
ORDER BY `Post`.`created` DESC

SELECT 
    `Attachment`.`id`, 
    `Attachment`.`post_id`, 
    `Attachment`.`created` 
FROM 
    `attachments` AS `Attachment` 
WHERE 
    `Attachment`.`post_id` IN (1, 2, 3, ..., n) 

我想要什么

我想将关系扩展如下:

Post hasMany Attachment;每个Attachment 属于 Type

而且我不知道要让 CakePHP 跟随它。
基本上,我需要的是:

SELECT *, `Post`.`id` 
FROM `posts` AS `Post` 
WHERE 1 = 1 
ORDER BY `Post`.`created` DESC

SELECT 
    `Attachment`.`id`, 
    `Attachment`.`post_id`, 
    `Attachment`.`created`, 
    `Type`.`title`, `Type`.`icon` 
FROM 
    `attachments` AS `Attachment` 
LEFT JOIN 
    `types` AS `Type` 
    ON (`Attachment`.`type_id`=`Type`.`id`) 
WHERE 
    `Attachment`.`post_id` IN (1, 2, 3, ..., n) 

注意添加的LEFT JOIN types

所以我在第二个查询中得到了相应的类型数据。我知道我可以循环获取数据或使用->query() 调用,但我希望这尽可能有效和灵活。

问题

我尝试了ContainableModel Unbinding trick (and this one) 但没有成功。我尝试了不同的选项组合,我相信我什至删除了连接。这是我的PostsController 现在的样子。

class PostsController extends AppController {
    public function index() {

        $this->Post->unbindModel(array('hasMany' => array('Attachment')));
        $this->Post->Attachment->unbindModel(array('belongsTo' => array('Type')));

        $this->Post->bindModel(array(
            'hasMany' => array(
                'Attachment' => array(
                    'className'  => 'Attachment',
                // when uncommented, throws the "Unknown column Post.id" SQLSTATE error
                //  'conditions' => array('Post.id' => 'Attachment.post_id'),
                    'foreignKey' => false,
                ),
            ),
        ));
        $this->Post->Attachment->bindModel(array(
            'belongsTo' => array(
                'Filetype' => array(
                    'className'  => 'Filetype',
                //  'conditions' => array('Type.id' => 'Attachment.type_id'),
                    'foreignKey' => false,
                ),
            ),
        ));
        $all = $this->Post->find('all', array(
            'joins' => array(
                array(
                    'table' => 'users',
                    'prefix' => '',
                    'alias' => 'User',
                    'type' => 'INNER',
                    'conditions' => array(
                        'User.id = Post.user_id',
                    )
                ),
            ),
            'contain' => array('Attachment', 'Type'),
            'conditions' => array(),
            'fields' => array('*'),
            'order' => 'Post.created ASC'
        ));
        var_dump($all);exit;
    }
}

但它只是在循环中的每次迭代中运行一个额外的查询并获取所有附件:

SELECT `Attachment`.`id`, ... 
FROM `attachments` AS `Attachment` 
WHERE 1 = 1 

当我取消注释此关联的条件时,它会抛出 SQLSTATE “Column Post.id not found error” - 我猜是因为这里没有加入 Post 表。

我需要帮忙设置一下。

请帮忙!谢谢

更新

我已将控制器更改如下。请注意没有bindModel/unbindModel 代码,关系是在模型类中设置的(在这种情况下是否正确?)。

class PostsController extends AppController {
    public function index() {
        $options = array(
            'contain' => array(
                'Post',
                'Type'
            ),
            'order' => 'Post.created DESC',
            'conditions' => array(
            //  'Post.title LIKE' => 'my post'
            )
        );

    //  The following throws "Fatal error: Call to a member function find() on a non-object"
    //  $posts = $this->Attachment->find('all', $options); 

    //  So I had to use $this->Post->Attachment instead of $this->Attachment
        $posts = $this->Post->Attachment->find('all', $options);
        $this->set(compact('posts'));
    }   
}

这是Attachment 型号:

class Attachment extends AppModel {
    public $belongsTo = array(
        'Type' => array(
            'className' => 'Type',
            'foreignKey' => 'type_id',
        ),
        'Post' => array(
            'className' => 'Post',
            'foreignKey' => 'post_id',
        ),
    );
}

上面的代码运行这个查询:

SELECT 
    `Attachment`.`id`, `Attachment`.`type_id`, `Attachment`.`post_id`, `Attachment`.`created`, 
    `Type`.`id`, `Type`.`title`, 
    `Post`.`id`, `Post`.`text`, `Post`.`created` 
FROM 
    `attachments` AS `Attachment` 
    LEFT JOIN `types` AS `Type` ON (`Attachment`.`type_id` = `Type`.`id`) 
    LEFT JOIN `posts` AS `Post` ON (`Attachment`.`post_id` = `Post`.`id`) 
WHERE 
    1 = 1 
ORDER BY 
    `Post`.`created` ASC

这里的一切都是关于附件的。我的意思是帖子加入了附件,所以如果帖子没有附件,它不会被退回。这可能是因为电话是Attachment->find(),所以从附件的角度来看。我想应该是:

// ...
FROM 
    `posts` AS `Post`
    LEFT JOIN `attachments` AS `Attachment`  ON (`Attachment`.`post_id` = `Post`.`id`) 
    LEFT JOIN `types` AS `Type` ON (`Attachment`.`type_id` = `Type`.`id`) 
// ...

但它不会起作用,是吗?您会看到postsattachmentstypes,但它们确实具有不同 关系类型。最初,我已经发布了 CakePHP 运行的这两个单独的查询 - 这一定是有原因的。

更新2

我仍然相信在初始设置中将第二个查询更改为Attachment 模型(请参阅我想要的部分)。所以我会得到附件类型以及附件本身。我的意思是在这种情况下将types 表左连接到attachments 不会破坏任何数据库关系逻辑,是吗?
我只是想确保无法通过一个复杂但单一的find() 调用来做到这一点。

【问题讨论】:

    标签: php mysql cakephp has-many belongs-to


    【解决方案1】:

    每当 Cake 看到 hasMany 关系时,它会自动创建多个查询来提取数据。在构建这些查询时,它会查找可以左连接到它的关系(hasOne 和 belongsTo)。

    由于 Cake 无法为您执行此操作,您需要自己合并它们。

    public function index() {
      $posts = $this->Post->find('all');
      // get all attachments for all found posts
      $attachments = $this->Post->Attachment->find('all', array(
        'contain' => array('Type'),
        'conditions' => array('Post.id' => Set::extract('/Post/id', $posts)
      ));
      // now join them to the posts array
      foreach ($posts as $key => $data) {
        $postId = $data['Post']['id'];
        // append any data related to this post to the post's array
        $posts[$key] += Set::extract("/Attachment[post_id=$postId]/..", $attachments);
      }
      $this->set(compact('posts'));
    }
    

    这不是最有效的方法,因为您将多次遍历 $attachments 数组,但我相信您明白了。

    【讨论】:

    • 感谢您的回复,但我如何加入那里的用户表?
    • 只需将其添加到 contain 数组的适当位置即可。 IE。如果属于Post,则'contain' => array('Post' => array('User'), 'Type')
    • var_dump'd $posts 数组。它有 20 个项目,每个项目都有 PostAttachment 子数组,没有 Type。请不要误会我的意思,但这是使用分页特定功能的解决方法吗?我只需要以某种方式加入表格并获取数据。也许我错过了Containable 的一些东西?谢谢
    • 我刚刚删除了分页引用。请记住,分页调用只是一个稍微特殊的查找调用(它添加了适当的限制等),因此大多数选项都可以用于常规查找调用。我错误地假设了分页,这就是为什么你只得到 20 个结果。这一切都假设 Type 已经与 Attachment 模型相关(即,在关系 var 中或通过 bindModel 绑定)。
    • 你是对的。不幸的是,使用 ORM 无法实现您想要做的事情,除非您通过绑定/取消绑定将 Attachment 从 hasMany 关系更改为 hasOne 关系(我可以向您展示一个示例)。但是,这也可能产生不希望的结果,因为它只拉取一个附件。我认为解决您的问题的最佳方法是使用两个 Cake find 调用并自己合并它们,因为 Cake 不会按照您需要的方式完成。
    【解决方案2】:

    试试 hasMany 中的 finderQuery。 例如: 在 Post 模型中,

    public $hasMany = array(
        'Attachment' => array(
            'className' => 'Attachment',
            'foreignKey' => 'post_id',
            'dependent' => false,
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'exclusive' => '',
            'finderQuery' => '                            
                 SELECT 
                     `Attachment`.`id`, 
                     `Attachment`.`post_id`, 
                     `Attachment`.`created`, 
                     `Type`.`title`, 
                     `Type`.`icon` 
                      FROM 
                     `attachments` AS `Attachment` 
                      LEFT JOIN 
                     `types` AS `Type` 
                      ON (`Attachment`.`type_id`=`Type`.`id`) 
                       WHERE 
                              `Attachment`.`post_id` IN (1, 2, 3, ..., n)
                    ',
            'counterQuery' => ''
        )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-22
      • 2012-03-30
      • 1970-01-01
      • 2012-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多