I am looking to create a custom product collection using specific product attributes as filters. The product collection I am getting has well over 100 products in which is fine but I need to find a way of choosing 5 products at random from the collection and then load them to display on a page. Has anyone developed anything similar to this or know the best way of implementing this?
-
If you want to show random products, take care of the performance. Most of the times, Magento optimize loading by caching objects and blocks. But if you explicitely want to show random products each time the page is seen, you'll force the whole pile of the framework to load.– PilouMar 17, 2015 at 13:39
-
Hi Pilou, Thanks for the heads up on the performance side of it. Will definitely take this into consideration.– Ryan CopelandMar 24, 2015 at 11:16
2 Answers
I found out that ORDER BY RAND()
is very unperformant on a Magento collection because all data gets copied to a temp table, assigned a random number and then sorted without index. If we reduce the data to be copied to just the ID, it gets a lot faster (still not perfect because the slow sorting remains but OK for a small catalog like yours).
For this, I made a modified version of getAllIds()
:
$numberOfItems = 5;
// Step 1: Preselect ids using ORDER BY RAND()
// Filters are applied here
$productCollection = Mage::getModel('catalog/product')
->getCollection();
$productCollection
->addStoreFilter()
->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds());
$productCollection->getSelect()->order('RAND()');
$idsSelect = clone $productCollection->getSelect();
$idsSelect->reset(Zend_Db_Select::LIMIT_COUNT);
$idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
$idsSelect->reset(Zend_Db_Select::COLUMNS);
$idsSelect->columns('e.' . $productCollection->getEntity()->getIdFieldName());
$idsSelect->limit($numberOfItems, null);
$idsSelect->resetJoinLeft();
$accessor = new ReflectionObject($productCollection);
$_bindParams = $accessor->getProperty('_bindParams');
$_bindParams->setAccessible(true);
$chosenIds = $productCollection->getConnection()
->fetchCol($idsSelect, $_bindParams->getValue($productCollection));
Then I could load the products by these random ids:
// Step 2: Load products
// Attributes and index data are joined here
$productCollection->addIdFilter($chosenIds);
$productCollection
->addMinimalPrice()
->addFinalPrice()
->addTaxPercents()
->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
->addUrlRewrite();
$productCollection->load();
Simpler alternative
An alternative that also works well on small catalogs (<10000 products) is to load all ids and select the random IDs with PHP:
$numberOfItems = 5;
$productCollection = Mage::getModel('catalog/product')->getCollection();
$productCollection
->addStoreFilter()
->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds());
$candidateIds = $productCollection->getAllIds();
$numberOfProducts = count($candidateIds);
$chosenIds = [];
while ($numberOfItems) {
$randomKey = mt_rand(0, $numberOfProducts - 1);
if (!isset($chosenIds[$randomKey])) {
chosenIds[$randomKey] = $candidateIds[$randomKey];
--$numberOfItems;
}
}
$productCollection->addIdFilter($chosenIds);
$productCollection
->addMinimalPrice()
->addFinalPrice()
->addTaxPercents()
->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
->addUrlRewrite();
$productCollection->load();
You can read a more in depth analysis and see some benchmarks in my blog: http://www.schmengler-se.de/en/2015/09/show-random-products-in-magento-you-are-doing-it-wrong/
try
$collection->getSelect()->order(new Zend_Db_Expr('RAND()'));
$collection->getSelect()->limit(5);