Результаты разбивки на страницы, отфильтрованные по условию в связанной модели (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