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

Мне нужно разбить список Product s, относящихся к конкретному 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 было бы (как предложено Dave ) с использованием объединений. Этот пост предлагает очень удобный помощник для сборки $this->paginate['joins'] для разбивки на ассоциации HABTM.

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

Ответы [ 2 ]

6 голосов
/ 10 июля 2011

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

Принудительно 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[] (как Aziz *).1025 * показал в своем ответе), чтобы исключить повторяющиеся записи (будет работать только на MySQL).

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

1 голос
/ 10 июля 2011

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

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

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

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

...