Результаты разбивки на страницы, отфильтрованные по условию в связанной модели (HABTM) с использованием Containable

Мне нужно разбить список Product принадлежность к конкретным Category (Ассоциация HABTM).

В моем 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 Products которые принадлежат Category с id = 3)

Примечание: возможное решение без Containable будет (как предложил Дэйв) использовать соединения. Этот пост предлагает очень удобный помощник для создания $this->paginate['joins'] разбивать на ассоциации HABTM.

Примечание: все еще ищите более элегантное решение, используя Containable чем подделка hasOne связывание.

2 ответа

Решение

Наконец-то я нашел способ сделать то, что хочу, поэтому разместил его как ответ:

Чтобы заставить JOIN (и быть в состоянии фильтровать по условию на связанной модели) в Containable - ты должен использовать подделку 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());

Заметка false в качестве второго аргумента bindModel - что делает связывание постоянным. Это нужно, потому что paginate() проблемы find('count') до find('all'), что бы сбросить временную привязку. Так что вы можете захотеть вручную unbindModel после этого.

Кроме того, если ваше условие включает в себя несколько идентификаторов в связанной модели HABTM, вы можете добавить 'group' => 'Product.id' в ваш $this->paginate[] (как показал Азиз в своем ответе) для устранения дублирующих записей (будет работать только на MySQL).

ОБНОВЛЕНИЕ: Однако у этого подхода есть один серьезный недостаток по сравнению с подходом объединений (предложенным Дейвом): условие может применяться только к внешнему ключу промежуточной модели (category_id в моем случае); если вы хотите использовать условие в любом другом поле в связанной модели - вам, вероятно, придется добавить другое bindModel('hasOne'), связывание промежуточной модели с моделью, связанной с HABTM.

Когда вы помещаете условие во вложенный контейнер, вы просите его извлечь только категории с этим идентификатором. Так что - он делает то, что вы просите, но это не то, что вы хотите.

Хотя кажется, что это возможно, единственная удача, которую я испытал, делая то, что вы пытаетесь сделать (после МНОГИХ часов и нескольких вопросов о стекопереработке), - это объединение, а не контейнер.

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

Это не совсем та же проблема, но вы можете просмотреть часть моего кода, где я запрашиваю условия HABTM (я ответил на мой вопрос внизу) здесь: Выберите Все события с Событием-> Расписание-> Дата между датами начала и окончания в CakePHP

Другие вопросы по тегам