Random products from same category with parent category fill up

The question:

I would like to do following on

Getting 12 random upsell-products from same category as current product.
They have to be saleable.
They shouldnt be in related-products already.
They shouldnt be in cart.

And if there are not 12 in that category fill the rest with products from parent category.

I think this is getting complex but I can do it. Just not sure if I can do it good 🙂

My Question is do I have to worry about performance with that select complexity? What do you mean?

Code examples would be great that I can compare them.

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

I recently implemented something very similar and will share the relevant code below. But first some comments about performance, since that was the question:

General thoughts on performance for your requirements:

  • “they shouldn’t be in cart” means, you have only limited means to cache the upsell block. I strongly recommend to do it anyway, but set cache key and cache lifetime only if the cart is empty.
  • Don’t ORDER BY RAND() because it results in a resource intensive temp table copy. It has to load all results into a temporary table, assign a random number to each row and then sort without any index. Instead we retrieve all candidate ids (this is faster and the amount of data is managable even for large catalogs), pick some randomly and retrieve these rows directly. You can read about it in detail in my blog: http://www.schmengler-se.de/en/2015/09/show-random-products-in-magento-you-are-doing-it-wrong/

Specific performance problems with your solution

  • Don’t use addAttributeToSelect('*') with EAV collections, only select what you need
  • Not entirely sure, but I doubt that new Zend_Db_Expr('FIELD(category_id, ' . implode(',', $_cat_order).')')) can efficiently use the MySQL indexes

Complete Solution

This is an observer for the catalog_product_upsell event. It uses the product collection provided by the product’s category because I didn’t need to fall back to the parent, but I’m sure you can adjust it.

use Mage_Catalog_Model_Product as Product;
use Mage_Catalog_Model_Product_Link as RelatedProduct;
use Mage_Catalog_Model_Resource_Product_Link_Product_Collection as RelatedProductCollection;

class IntegerNet_AutoUpsell_Model_Observer
     * @see event catalog_product_upsell
     * @param Varien_Event_Observer $observer
     * @throws Mage_Core_Exception
    public function fillUpsellCollection(Varien_Event_Observer $observer)
        $collection = $observer->getCollection();
        if ($collection instanceof RelatedProductCollection
            && $collection->getLinkModel()->getLinkTypeId() === RelatedProduct::LINK_TYPE_UPSELL
            && $collection->count() < $observer->getLimit('upsell')
        ) {
            $this->addItemsFromCategory($collection, $observer->getLimit('upsell') - $collection->count(), $observer->getProduct());
    protected function addItemsFromCategory(RelatedProductCollection $collection, $numberOfItems, Product $product)
        /** @var Mage_Catalog_Model_Resource_Product_Collection $productsToAdd */
        $productsToAdd = $this->_getProductCategory($product)->getProductCollection();
            ->addIdFilter(array_merge([$product->getId()], $collection->getAllIds()), true)

        $candidateIds = $productsToAdd->getAllIds();
        $choosenIds = [];
        $maxKey = count($candidateIds)-1;
        while (count($choosenIds) < $numberOfItems)) {
            $randomKey = mt_rand(0, $maxKey);
            $choosenIds[$randomKey] = $candidateIds[$randomKey];
        foreach ($productsToAdd as $product) {

     * @param Mage_Catalog_Model_Product $product
     * @return Mage_Catalog_Model_Category
    protected function _getProductCategory(Product $product)
        $category = $product->getCategoryCollection()
        return $category;

Some parts that I’d like to highlight:

->addIdFilter(array_merge($product->getId(), $collection->getAllIds()), true)

This excludes the product itself and the products that are already manually defined as upsell products (I leave them and only fill the blanks with random products)


This prepares the product collection to load the necessary data to display prices, the product link and any attributes configured as “used in product listing”, but not more.

Method 2

Here is my working code:

//get needed variables
$_current_prod = Mage::registry('current_product');
$_current_cat =  Mage::getModel('catalog/layer')->getCurrentCategory();
$_current_cat_id = $_current_cat->getId();
$_parent_cat_id =  $_current_cat->getParentId();
//did array because later on it may get more complex order with more ids or I want to sort it different
$_cat_order = array($_current_cat_id,$_parent_cat_id);
//getting related products to filter from collection
$_related_products = $_current_prod->getRelatedProductIds();

//getting cart items to filter from collection
$_session= Mage::getSingleton('checkout/session');
$_cart_products = array();
foreach($_session->getQuote()->getAllItems() as $_item)
   $_cart_products[] = $_item->getProductId();

//merge product arrays to filter from collection
$_inuse_products = array_unique(array_merge($_related_products, $_cart_products));      

//getting collection
$_productCollection = Mage::getModel('catalog/product')
->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id = entity_id', null, 'left')
->addAttributeToFilter('entity_id', array('nin' => $_inuse_products))
->addAttributeToFilter('entity_id', array('neq' => $_current_prod->getId()))
->addAttributeToFilter('category_id', array(
    array('finset' => $_current_cat_id),
    array('finset' => $_parent_cat_id))

//sort collection = deepest category level first and limit it to 12
$_productCollection->getSelect()->order(new Zend_Db_Expr('FIELD(category_id, ' . implode(',', $_cat_order).')'))->limit(12);

I guess there is some to optimize? 🙂

Method 3

Put this to View.phtml it will work. just need to adjust the css

<?php $categories = $_product->getCategoryIds(); ?> 
<?php $result = array(); 
foreach($categories as $cat_id) { 
$category = Mage::getModel('catalog/category'); 
$collection = $category->getProductCollection(); 
foreach ($collection as $product) { 
$result[] = $product->getId(); 
<div class="box-others-also-like" style=" margin:0 auto;"> 
<?php if(sizeof($result) >= 10) 
$ourneed = array_rand($result,10); 
foreach($ourneed as $cc) 
$thisproduct= Mage::getModel('catalog/product')->load($result[$cc]); 
<li style=" border:1px solid #ccc; float:left; width:140px; margin:10px 5px 0 0; overflow:hidden;"> 
<a href="<?php echo $thisproduct->getProductUrl(); ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" title="<?php echo $thisproduct->getName(); ?>" ><img src="<?php echo $this->helper('catalog/image')->init($thisproduct,'small_image')->resize(140) ?>" width="140" height="140" alt="<?php echo $thisproduct->getName(); ?>" /></a> 
<a href="<?php echo $thisproduct->getProductUrl(); ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" title="<?php echo $thisproduct->getName(); ?>" title="<?php echo $thisproduct->getName(); ?>" >
<h3 style=" font:12px/1.55 arial; width:140px; height:4.25em; overflow:hidden;"><?php echo $thisproduct->getName(); ?></h3></a> 
<?php } ?> 
<?php }else { 
foreach($result as $cc) 
$thisproduct= Mage::getModel('catalog/product')->load($cc); 
<a href="<?php echo $thisproduct->getProductUrl(); ?>" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener" title="<?php echo $thisproduct->getName(); ?>" ><img src="<?php echo $this->helper('catalog/image')->init($thisproduct, 'small_image')->resize(200) ?>" width="200" height="200" alt="<?php echo $thisproduct->getName(); ?>" /></a> </li> 
<?php } 

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment