Como filtrar produtos que NÃO estão em categorias?


10

Aqui está o meu código:

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

Desejo que todos os produtos não estejam na lista de códigos de categoria, mas meu código não forneceu o resultado esperado. Por favor, mostre-me o caminho, obrigado.


qual foi o resultado esperado versus os resultados obtidos?
ahnbizcad

Respostas:


16

Você precisa ingressar na tabela que contém as relações categoria / produto.

Uma variação da coleção que eu uso para encontrar todos os produtos em uma lista de categorias deve fazer o truque para você:

(não testado, mas deve colocá-lo no caminho certo)

$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
Isso não parece funcionar se um produto aparecer em mais de uma categoria. O 'nin' simplesmente exclui essa linha, mas o ID do produto ainda será exibido para as outras categorias.
greatwitenorth

Olá, como está o código, ele funciona 100%. O código escrito excluirá o produto conforme a lista de categorias fornecida na variável $ catIds. Se o produto aparecer em mais de uma categoria e essa categoria não estiver incluída na lista de exclusão, bem, ele aparecerá. A culpa é que você não está excluindo todas as categorias. O código não pode magicamente (como está escrito) saber em que outras categorias o produto aparece em>
ProxiBlue 6/16

Parece mais que você deseja um meio de excluir por IDs de produto, que você pode derivar facilmente do código acima. A primeira dica seria trocar para IN, obter os IDs do produto resultantes dessa consulta e usá-los em uma nova coleção de produtos, excluindo os produtos fornecidos por ID. Ainda melhor é transformá-lo em uma subconsulta da coleção de produtos excluída pelos IDs do produto.
ProxiBlue

5

O código a seguir funcionará para você:

$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

Eu encontrei uma maneira um pouco melhor de fazer isso, usando um anti-join (Magento 1.9).

Benefícios desta abordagem

O benefício disso em relação à resposta original é que você não obterá falsos positivos e, como resultado, é mais rápido e menos propenso a erros. Por exemplo, suponha que você tenha um único produto:

  1. Camisa (nas categorias: 1 | 2)

Você deseja "encontrar todos os produtos que não estão dentro category 3e adicioná-los a category 3" . Então, você executa uma NOT INconsulta e ela retornará duas linhas (name | category_id):

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

Não é grande coisa, o Magento ainda retornará apenas o primeiro resultado e você o adicionará. Exceto ! Na segunda vez que essa consulta é executada, você tem os mesmos resultados:

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

E o Magento lhe dirá que você ainda não adicionou esta camisa category 3. Isso ocorre porque quando um produto pertence a várias categorias, ele tem várias linhas na tabela "catalog_product_entity" . E assim um LEFT JOINretornará vários resultados.

Isso é indesejável porque

  1. Seu conjunto de resultados será maior que o necessário, o que consumirá mais memória do que o necessário. Especialmente se você tiver um estoque muito grande de milhares de itens.
  2. Você precisará fazer uma verificação adicional no PHP para determinar se os resultados são falsos positivos (por exemplo, in_array($categoryThree, $product->getCategories())), o que significa que você percorrerá resultados desnecessários. Isso tornará seu script / código mais lento, especialmente em grandes inventários.

Solução

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

A consulta SQL gerada será semelhante a:

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

Explicação:

Dadas as tabelas de relacionamento de produto e categoria de produto <=>:

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

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

Sua consulta está dizendo "dê-me todas as linhas em " catalog_product_entity " e cole na coluna" category_id "de " catalog_category_product " . Depois, basta me fornecer as linhas que category_id = 124" .

Por ser uma junção esquerda, ela sempre terá as linhas de "catalog_product_entity" . Para qualquer linha que não possa ser correspondida, será NULL:

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

A partir daí, a consulta então diz: "ok, agora me dê tudo onde o category_id é NULL" .


1

Não é tão fácil quanto pode parecer.

Aqui está a opção baseada em GROUP_CONCAT, pois seu limite padrão (1024, mas pode ser aumentado, é claro) deve estar bem com os IDs de categoria de produto separados por vírgulas.

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

Além disso (se você não gosta de GROUP_CONCAT), pode usar WHERE product_id NOT IN em uma subconsulta de um ID de produto, na verdade, estão em categorias que você precisa excluir (não fornecendo aqui).

A abordagem anti-junção de outra resposta também funcionará. Mas, neste caso, você não pode adicionar facilmente condições adicionais.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.