¿Cómo filtrar productos que NO ESTÁN EN categorías?


10

Aquí está mi código:

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

Quiero obtener todos los productos que no están en la lista de identificadores de categoría, pero mi código no dio el resultado esperado. Por favor muéstrame el camino, gracias.


¿Cuál fue el resultado que esperaba frente a los resultados que obtuvo?
ahnbizcad

Respuestas:


16

Debe unirse a la tabla que contiene las relaciones categoría / producto.

Una variación de la colección que uso para encontrar todos los productos EN una lista de categorías debería ser la solución para usted:

(no probado, pero debería llevarte por el camino correcto)

$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
Esto no parece funcionar si un producto aparece en más de una categoría. El 'nin' simplemente excluye esa fila, pero la identificación del producto seguirá apareciendo para las otras categorías.
greatwitenorth

Hola, tal como está el código, funciona al 100%. El código escrito excluirá el producto según la lista de categorías dada en la variable $ catIds. Si el producto aparece en más de una categoría, y esa categoría no está incluida en la lista de exclusión, bueno, aparecerá. El error es que no está excluyendo todas las categorías. El código no puede mágicamente (como está escrito) saber en qué otras categorías aparece el producto en>
ProxiBlue

Suena más como si quisieras un medio para excluir por identificadores de producto, que puedes derivar fácilmente del código anterior. El primer paso sería cambiar a IN, luego obtener los ID de producto resultantes de esta consulta, y luego usarlo en una nueva colección de productos, excluyendo los productos dados por id. Aún mejor es convertirlo en una subconsulta de la colección de productos que se excluye por ID de producto.
ProxiBlue

5

El siguiente código funcionará para usted:

$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

He encontrado una forma algo mejor de hacerlo, usando un anti-join (Magento 1.9).

Beneficios de este enfoque

El beneficio de esto sobre la respuesta original es que no obtendrá falsos positivos, y como resultado es más rápido y menos propenso a errores. Por ejemplo, suponga que tiene un solo producto:

  1. Camisa (en categorías: 1 | 2)

Desea "encontrar todos los productos que no están en él category 3y luego agregarlos a category 3" . Entonces ejecuta una NOT INconsulta, y devolverá dos filas (name | category_id):

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

No es gran cosa, Magento solo devolverá el primer resultado y luego lo agregará. Excepto ! La segunda vez que se ejecuta esta consulta, tiene los mismos resultados:

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

Y Magento te dirá que todavía no le has agregado esta camisa category 3. Esto se debe a que cuando un producto pertenece a varias categorías, tendrá varias filas en la tabla "catalog_product_entity" . Y entonces a LEFT JOINdevolverá múltiples resultados.

Esto no es deseable porque

  1. Su conjunto de resultados será más grande de lo necesario, lo que utilizará más memoria de la necesaria. Especialmente si tienes un inventario muy grande de miles de artículos.
  2. Tendrá que hacer una verificación adicional en PHP para determinar si los resultados son falsos positivos (por ejemplo, in_array($categoryThree, $product->getCategories())), lo que significa que pasará a través de resultados innecesarios. Esto hará que su script / código sea más lento, especialmente con grandes inventarios.

Solución

// 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]
    ]);

La consulta SQL generada se verá así:

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`;

Explicación:

Dadas las tablas de relación producto y producto <=> categoría:

catalog_product_entity +-----------+ | ENTITY_ID | +-----------+ | 423 | | 424 | | 425 | +-----------+

catalog_category_product +-------------+------------+ | CATEGORY_ID | PRODUCT_ID | +-------------+------------+ | 3 | 423 | | 123 | 424 | | 3 | 425 | +-------------+------------+

Su consulta dice: " deme todas las filas en " catalog_product_entity " y péguelo en la columna" category_id "de " catalog_category_product " . Luego, solo deme las filas que category_id = 124" .

Debido a que es una combinación izquierda, siempre tendrá las filas de "catalog_product_entity" . Para cualquier fila que no pueda coincidir, será NULL:

Resultado +-------------+-------------+ | ENTITY_ID | CATEGORY_ID | +-------------+-------------+ | 423 | NULL | | 424 | 123 | | 425 | NULL | +-------------+-------------+

A partir de ahí, la consulta dice: "ok, ahora dame todo donde el category_id es NULL" .


1

No es tan fácil como parece.

Aquí está la opción basada en GROUP_CONCAT, ya que su límite predeterminado (1024 pero podría aumentarse, por supuesto) debería estar bien con los ID de categoría de producto separados por comas.

$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);
}

Además (si no le gusta GROUP_CONCAT) puede usar WHERE product_id NOT IN en una subconsulta de un ID de producto que en realidad está en las categorías que necesita excluir (no proporcionarlo aquí).

El enfoque anti-unión de otra respuesta también funcionará. Pero en este caso no puede agregar fácilmente condiciones adicionales.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.