使用Containable通过相关模型(HABTM)上的条件过滤分页结果

时间:2011-07-09 22:43:51

标签: cakephp cakephp-1.3 has-and-belongs-to-many paginate containable

我需要对属于特定Product(HABTM关联)的Category的列表进行分页。

在我的Product模型中

var $actsAs = array('Containable');
var $hasAndBelongsToMany = array(
    'Category' => array(
        'joinTable' => 'products_categories'
    )
);

并在ProductsController

$this->paginate = array(
    'limit' => 20,
    'order' => array('Product.name' => 'ASC'),
    'contain' => array(
        'Category' => array(
            'conditions' => array(
                'Category.id' => 3
            )
        )
    )
);
$this->set('products', $this->paginate());

但是,生成的SQL看起来像这样:

SELECT COUNT(*) AS `count` 
FROM `products` AS `Product` 
WHERE 1 = 1;

SELECT `Product`.`*` 
FROM `products` AS `Product` 
WHERE 1 = 1 
ORDER BY `Product`.`name` ASC 
LIMIT 20;

SELECT `Category`.`*`, `ProductsCategory`.`category_id`, `ProductsCategory`.`product_id` 
FROM `categories` AS `Category` 
JOIN `products_categories` AS `ProductsCategory` ON (`ProductsCategory`.`product_id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) AND `ProductsCategory`.`category_id` = `Category`.`id`)
WHERE `Category`.`id` = 3

(即,它选择20 Products,然后查询其Categories

虽然我需要

SELECT COUNT(*) AS `count` 
FROM `products` AS `Product` 
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3;

SELECT `Product`.*, `Category`.*
FROM `products` AS `Product` 
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3
ORDER BY `Product`.`name` ASC 
LIMIT 20;

(即选择属于Products的前20名Category id = 3)

注意: 没有Containable的可能解决方案是使用连接(如 Dave 建议的那样)。 This帖子提供了一个非常方便的助手来构建$this->paginate['joins']以对HABTM关联进行分页。

注意:仍在使用Containable寻找比假hasOne绑定更优雅的解决方案。

2 个答案:

答案 0 :(得分:6)

最后,我找到了一种方法来做我想做的事情,所以将其作为答案发布:

JOIN - you've got to use fake hasOne association中强制Containable(并且能够通过相关模型上的条件进行过滤)。

就我而言,ProductsController中的代码应为:

$this->Product->bindModel(array('hasOne' => array('ProductsCategory')), false);

$this->paginate = array(
    'limit' => 20,
    'order' => array('Product.name' => 'ASC'),
    'conditions' => array(
        'ProductsCategory.category_id' => $category
    ),
    'contain' => 'ProductsCategory'
);

$this->set('products', $this->paginate());

注意false作为bindModel的第二个参数 - 这使得绑定持久化。这是必需的,因为paginate()find('count')之前发出find('all'),这将重置临时绑定。因此,您可能希望之后手动unbindModel

此外,如果您的条件在HABTM关联模型中包含多个ID,您可能希望将'group' => 'Product.id'添加到$this->paginate[](正如 Aziz 已在其答案中显示)消除重复的条目(仅适用于MySQL)。

<强>更新 但是,与连接方法(由 Dave 建议)相比,这种方法有一个严重的缺点:条件只能应用于中间模型的外键(在我的情况下为category_id);如果要在关联模型中的任何其他字段上使用条件 - 您可能必须将另一个bindModel('hasOne')绑定中间模型添加到HABTM关联模型。

答案 1 :(得分:1)

当您将条件放入嵌套的Contain中时,您要求它仅检索具有该ID的类别。所以 - 它正在做你所要求的,但那不是你想要的。

虽然看起来应该是可能的,我唯一的运气就是你做了什么(经过多个小时和一些stackoverflow问题)是通过联接而不是包含

http://book.cakephp.org/view/1047/Joining-tables

这不是完全相同的问题,但你可以查看我的一些代码,我在这里查询HABTM条件(我在底部回答了我的问题):Select All Events with Event->Schedule->Date between start and end dates in CakePHP