Як фільтрувати продукти НЕ в категоріях?


10

Ось мій код:

$catIds = array(7,8,9);
$collection = Mage::getModel('catalog/product')->getCollection()
    ->addAttributeToSelect("*");
    ->addAttributeToFilter('category_ids', array('nin' => $catIds));

Я хочу, щоб усі товари не були в списку ідентифікаторів категорії, але мій код не дав очікуваного результату. Будь ласка, покажіть мені шлях, дякую.


який результат ви очікували від результатів, які ви отримали?
ahnbizcad

Відповіді:


16

Вам потрібно приєднатись до таблиці, яка містить відносини категорії / продукту.

Варіант колекції, який я використовую для пошуку всіх продуктів у списку категорій, повинен зробити для вас хитрість:

(неперевірений, але повинен привести вас у правильний шлях)

$productCollection = Mage::getResourceModel('catalog/product_collection')
    ->setStoreId(0)
    ->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id=entity_id', null, 'left')
    ->addAttributeToFilter('category_id', array('nin' => $catIds))
    ->addAttributeToSelect('*');

$productCollection->getSelect()->group('product_id')->distinct(true);
$productCollection->load();

ref: http://www.proxiblue.com.au/blog/Collection_of_products_in_all_child_categories/


1
Схоже, це не працює, якщо продукт відображається у кількох категоріях. Значення "дев'ять" просто виключає один рядок, але ідентифікатор продукту все ж з'явиться для інших категорій.
greatwitenorth

Привіт, Як код стоїть, він працює на 100%. Написаний код буде виключати продукт відповідно до заданого списку категорій у змінній $ catIds. Якщо товар відображається у більш ніж одній категорії, а ця категорія не включена до списку виключень, добре, він з’явиться. Виною тоді є те, що ви не виключаєте всіх категорій. Код не може магічно (як написано) знати, в яких інших категоріях з'являється товар>
ProxiBlue

Звучить так, як ви хочете, щоб за допомогою засобів виключити ідентифікатори продукту, які ви можете легко отримати з наведеного вище коду. Першим tep було б поміняти на IN, потім отримати ідентифікатори продукту, що утворюють цей запит, а потім використовувати його в новій колекції для продуктів, виключаючи дані продукту за id. Ще краще - це зробити підзапис колекції продуктів, що виключає ідентифікатори продукту.
ProxiBlue

5

Наступний код буде працювати для вас:

$catIds = array(7,8,9);
$_productCollection = Mage::getModel('catalog/product')
                ->getCollection()
                ->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id = entity_id', null, 'left')
                ->addAttributeToFilter('category_id', array('nin' => array('finset' => $catIds)))
                ->addAttributeToSelect('*');

1

Я знайшов дещо кращий спосіб зробити це, використовуючи анти-приєднання (Magento 1.9).

Переваги такого підходу

Перевага цього в порівнянні з оригінальною відповіддю полягає в тому, що ви не отримаєте помилкових позитивних результатів, і це швидше і менш схильне до помилок. Наприклад, припустимо, що у вас є один продукт:

  1. Сорочка (у категоріях: 1 | 2)

Ви хочете "знайти всі продукти не в category 3, а потім додати їх до category 3" . Отже, ви запустите NOT INзапит, і він поверне два рядки (name | category_id):

1. "Shirt" | 1
2. "Shirt" | 2

Нічого страшного, Magento все одно поверне лише перший результат, а потім ви додасте його. За винятком ! Другий раз, коли цей запит запускається, у вас є ті самі результати:

1. "Shirt" | 1
2. "Shirt" | 2

І Magento скаже вам, що ви досі не додавали цю сорочку до category 3. Це тому, що коли продукт належить до декількох категорій, вони матимуть декілька рядків у таблиці "catalog_product_entity" . І таким чином LEFT JOINзаповіт поверне декілька результатів.

Це небажано, оскільки

  1. Ваш набір результатів буде більшим, ніж потрібно, що витратить більше пам'яті, ніж потрібно. Тим більше, якщо у вас дуже великий запас тисяч предметів.
  2. Вам потрібно буде зробити додаткову перевірку в PHP, щоб визначити, чи є результати помилковими (наприклад, in_array($categoryThree, $product->getCategories())), тобто, ви будете перебирати непотрібні результати. Це зробить ваш сценарій / код повільнішим, особливо з великими запасами.

Рішення

// All products not in this category ID
$notInThisCatId = '123';

$filteredProducts = Mage::getModel('catalog/product')->getCollection();
$filteredProducts
    ->joinField('category_id', 'catalog_category_product', 'category_id', 'product_id=entity_id', ['category_id'=>$notInThisCatId], 'left');
$filteredProducts
    ->addAttributeToFilter('category_id', [
        ['null' => true]
    ]);

Створений запит SQL буде виглядати так:

SELECT 
    DISTINCT `e`.*, `at_category_id`.`category_id` 
FROM `catalog_product_entity` AS `e`
LEFT JOIN `catalog_category_product` AS `at_category_id` 
    ON (at_category_id.`product_id`=e.entity_id) AND (at_category_id.category_id = '123')
WHERE (at_category_id.category_id IS NULL)
GROUP BY `e`.`entity_id`;

Пояснення:

З огляду на таблицю відносин продукту та продукту <=> категорії:

каталог_продукт_істотність +-----------+ | ENTITY_ID | +-----------+ | 423 | | 424 | | 425 | +-----------+

каталог_категорія_продукт +-------------+------------+ | CATEGORY_ID | PRODUCT_ID | +-------------+------------+ | 3 | 423 | | 123 | 424 | | 3 | 425 | +-------------+------------+

Ваш запит говорить: "дайте мені всі рядки в " catalog_product_entity " , і вставте в стовпець" category_id "з " catalog_category_product " . Потім просто дайте мені рядки, що категорія_id = 124" .

Оскільки це ліве з'єднання, воно завжди матиме рядки з "catalog_product_entity" . Для будь-яких рядків, які неможливо зіставити, це NULL:

Результат +-------------+-------------+ | ENTITY_ID | CATEGORY_ID | +-------------+-------------+ | 423 | NULL | | 424 | 123 | | 425 | NULL | +-------------+-------------+

Звідси запит каже: "нормально, тепер дайте мені все, де категорія_id - NULL" .


1

Не так просто, як це може здатися.

Ось варіант на базі GROUP_CONCAT, оскільки його обмеження за замовчуванням (1024, але його можна, звичайно, збільшити), повинно бути в порядку, якщо ідентифікатори категорії продуктів розділені комами.

$categoryIdsToExclude = array(1, 2, 4); // Category IDs products should not be in

$collection = Mage::getModel('catalog/product')->getCollection();

$selectCategories = $collection->getConnection()->select();
$selectCategories->from($collection->getTable('catalog/category_product'), array('product_id', 'category_id'));
$mysqlHelper = Mage::getResourceHelper('core');
$mysqlHelper->addGroupConcatColumn(
    $selectCategories,
    'category_ids_set',
    'category_id',
    ','
);
$selectCategories->group('product_id');

$collection->getSelect()->joinLeft(
    array('category_ids_set_table' => new Zend_Db_Expr('(' . $selectCategories->__toString() . ')')),
    'category_ids_set_table.product_id = e.entity_id',
    array('category_ids_set' => 'category_ids_set_table.category_ids_set')
);

foreach ($categoryIdsToExclude as $val) {
    $collection->getSelect()->where('NOT FIND_IN_SET(?, category_ids_set)', $val);
}

Крім того (якщо вам не подобається GROUP_CONCAT), ви можете використовувати WHERE product_id НЕ в підзапиті ідентифікаторів продукту насправді в категоріях, які потрібно виключити (не надаючи це тут).

Антиприєднання підхід з іншої відповіді також буде працювати. Але в цьому випадку ви не можете легко додати додаткові умови.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.