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



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

在我的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;

(即选择前20个属于CategoryProductsid=3(

注意:没有Containable的可能解决方案是(正如Dave建议的那样(使用联接。这篇文章提供了一个非常方便的助手来构建$this->paginate['joins'],以便在HABTM关联上进行分页。

注意:仍在寻找使用Containable而不是伪造hasOne绑定的更优雅的解决方案

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

要在Containable中强制JOIN(并能够通过关联模型上的条件进行过滤(,必须使用伪hasOne关联。

在我的情况下,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());

注意falsebindModel的第二个参数,它使绑定持久化。这是必要的,因为paginate()find('all')之前发布find('count'),这将重置临时绑定。因此,您可能需要在之后手动unbindModel

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

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

当您将条件放入嵌套的Contain中时,您要求它只检索具有该ID的Categories。因此,它正在执行您的要求,但这不是您想要的。

虽然这似乎是可能的,但我唯一的幸运是通过Joins而不是Contain来做你想做的事情(经过许多小时和几个堆叠的问题(。

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

这不是完全相同的问题,但你可以在这里浏览我的一些代码,我在其中查询HABTM条件(我在底部回答了我的问题(:选择带有事件的所有事件->日程安排->CakePHP 中开始和结束日期之间的日期

最新更新