Magento distinct(true) clause ignored - magento

Using the following code:
$collection = Mage::getModel('custom/custom')
->getCollection()
->distinct(true)
->addFieldToSelect('brand')
->load();
I've found the distinct is being ignored in 1.4.2, exactly the same code in 1.8.1 is working as I would expect (returning a single entry for each brand).
1.4.2 result:
"Ace"
"Ace"
1.8.1 result:
"Ace"
For myself I notice that 1.4.2 is returning an array of all fields from the model for each object , rather than just the one specified in the select (brand) as 1.8.1 does. I think this is what's causing the problem the distinct is included in the SELECT on both versions - in fact the SELECT statements are identical across versions.
Is there a way to get a distinct result for a single field in older versions of Magento?
EDIT
Based on the discussion below with liyakat my final solution was:
$collection = Mage::getModel('custom/custom')
->getCollection()
->distinct(true)
->addFieldToSelect('brand')
->setOrder('brand', 'ASC');
$collection->getSelect()->group('brand');
$collection->load();

you can add this function to get select statement of collection. and i am sure you will get result.
$collection = Mage::getModel('custom/custom')
->getCollection()
->addFieldToSelect('brand')
->load();
$collection->getSelect()->columns(
array('asbrand' => new Zend_Db_Expr(
"distinct(brand)")
)
);
hope this will work for you.

You could also write it like this
... $collection->getSelect()->group('product_id')->distinct(true);
Shorten up the code a tiny bit.

While adding the join make sure blank array is passed in the third parameter (only if you are adding a join).
$collection->getSelect()->joinLeft(
'sales_order_item',
'sales_order_item.order_id = main_table.order_id',
[]
);
After that add the following to apply DISTINCT
$collection->getSelect()->distinct(true);

Related

How can I sort a loaded product collection in Magento?

I want to sort a product collection that is already loaded by
$_productCollection = $this->getLoadedProductCollection();
The default sort in admin Magento is Style attribute
I want to sort first by Style, then by color and then by name.
I've try
$_productCollection->setOrder(array('style', 'color','name'), asc);
and also
$_productCollection->addAttributeToSort('color', Varien_Data_Collection::SORT_ORDER_ASC);
$_productCollection->addAttributeToSort('name', Varien_Data_Collection::SORT_ORDER_ASC);
but is not working.
The default sort is working good. Can someone please help?
You can do it this way:
$_productCollection = $this->getLoadedProductCollection();
$_productCollection->clear();
$_productCollection->addAttributeToSort('color', Varien_Data_Collection::SORT_ORDER_ASC);
foreach ($_productCollection as $product) {
// ...
}
This way the collection is forced to be reloaded and then your custom sorting is applied.
You can also take clone of the loaded collection and then modify the query as you want.
// clone of the current collection.Better way of modifying the colection
$_productCollection = clone $this->getLoadedProductCollection();
// unset the cuurent coolection and append your custom filter.
$_productCollection->clear()
->addAttributeToFilter('color', Varien_Data_Collection::SORT_ORDER_ASC)
->load();
Nothing here or elsewhere worked for me. No matter what I tried it would just not sort on /Magento_Catalog/templates/product/list.phtml using 'getLoadedProductCollection()'. Also had a weird issue where products would disappear after clearing cache, until a reindex was done. No idea what was going on there, something was not right.
So I got it to work in the hacky way below (Yes I know it's not 'best practice' to use object manager but it was the only thing that worked!!)
$_objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$categoryId = 2;
$category = $_objectManager->create('Magento\Catalog\Model\Category')->load($categoryId);
$productCollection = $_objectManager->create('Magento\Catalog\Model\ResourceModel\Product\CollectionFactory');
$_productCollection = $productCollection->create()
->addAttributeToSelect('*')
->setOrder('position', 'ASC')
->addCategoryFilter($category)->load();
If you have products in a collection that need more advanced sorting/oordering you can move the products to an assoc array which is much easier to sort/order as you like. Once the order is as you like you can add them back by first clearing your collection with $collection-removeAllItems() and then iterate your array as $product and move each product back to the collection with $collection->addItem($product);
I used this when I wanted a sticky that was not touched by different ordering/sorting.
You can use setOrder on collection to sort any collection data like below :
$_productCollection->setOrder('id','DESC');
Where id can be replace with your column name, & second parameter can be DESC & ASC as per your requirement.
Have it working in v2.3.7 using the following
$_productCollection = $block->getLoadedProductCollection();
$_productCollection->getSelect()->reset(Zend_Db_Select::ORDER);
$_productCollection->setOrder('price', 'ASC');

Magento Collection - setOrder and getFirstItem

I am writing a Plugin and added a own db table to the magento database.
Now i want to get some data in sorted order from the table and i added the code bellow.
But everytime i dump the result of the last line i get the data in the order they are in the table not sorted by the given parameter. Can anyone help me with this?
$collection = $this->getCollection();
$collection->setOrder(array('fieldname' => 'asc', 'fieldname' => 'desc'));
$collection->getFirstItem()->getData();
You can call setOrder function multiple times for different columns.
$collection = $this->getCollection()
->setOrder('fieldname', 'asc')
->setOrder('fieldname2');
Note: Direction DESC will be used by default.

Select only specific fields in Magento

I'm trying to use the MAX function of MySQL to retrieve the latest dates from my table.
$_updates = Mage::getModel('ticket/updates')->getCollection();
$_updates->getSelect()->columns('MAX(created) as max_created')->group(array('status_id'));
This is the resulting query:
SELECT `main_table`.*, MAX(created) AS `max_created` FROM `em_ticket_updates` AS `main_table` GROUP BY `status_id`
The problem with this is that if all the fields are included (main_table.*) it does not function correctly.
Is there a way to remove main_table.* from the query and only use specific fields?
Thanks.
A Zend trick can be used here.
$_updates->getSelect()
->reset(Zend_Db_Select::COLUMNS)
->columns('MAX(created) as max_created')
->group(array('status_id'));
NOTE:
For EAV collection you must re-add the entity_id or you will have an error when the collection is loaded.
$collection = Mage::getModel('catalog/product')
->getCollection();
$collection->getSelect()
->reset(Zend_Db_Select::COLUMNS)
->columns(array('entity_id'));
$collection
->addAttributeToSelect(array('image','small_image','thumbnail'))
->addFieldToFilter('entity_id', array('in' => $simple_ids));
Magento provide addFieldToSelect() fumctionality. Use below code to get specific field.
$Collection = Mage::getModel('showdown/votes')->getCollection();
$Collection->addFieldToSelect('id');
I recognise this is an old post but I thought I'd post an alternative solution.
I had a similar problem myself, I eventually searched through the source until I reached Zend_Db_Select After consulting the Zend Documentation (Example 8).
$select = $db->select()
->from(array('p' => 'products'),
array('product_id', 'product_name'));
Just need to use "getColumnValues"
$_updates = Mage::getModel('ticket/updates')->getCollection();
$columnValues = $_updates->getColumnValues('Your Column Name');

Magento filter products by items that have this item in their related products

I wish to get a collection of products that have product x as a related product.
So, say I'm starting with
$_productCollection = $product->getCollection()
->addAttributeToSelect('*')
->addAttributeToFilter('status',1)
->addStoreFilter()
->distinct(true);
And wish to add something like (but with some reverse in instead of eq):
$_productCollection->addAttributeToFilter('related_ids',array('eq' => $idofproductx));
I can't find anything, but assume it will include a join of some description. I'd rather avoid loading all products and then all related for each cycling through them all, for obvious reasons.
You may be able to do
// #var $products array
$_productCollection->getSelect()->join(array('links' => 'catalog_product_link'), 'e.entity_id = links.product_id');
$_productCollection->getSelect()->where('links.linked_product_id IN (?)', $products);
I tested that it runs without error against my DB, but I don't have product links in my database (we fetch from a web service), so I can't be certain it returns the expected products.
I recently had the same issue, but I ended up making a small module for this so it's reusable.
But for simplicity a stripped back version of my solution can be found below.
// load by a product Id
$_product = Mage::getModel('catalog/product')->load($_productId);
$_linkInstance = $_product->getLinkInstance();
$collection = $_linkInstance->getLinkCollection();
// get products which have $_productId as a link
$collection->addFieldToFilter('linked_product_id', array('eq' => $_product->getId()));
// 1 = related products
$collection->addLinkTypeIdFilter(1);
$collection->joinAttributes();
The above code will populate $collection with an array of IDs which have assigned $_productId as a related product.
Note: This works on CE 1.9.1.0

How to select specific fields with aliases using joinTable or joinField in Magento

I want to pre-filter* data in the invoice grid visible in Magento's admin panel.
Here is a question that I asked earlier, and this one is related to the solution presented for that, hence it might act as a good explanation.
So, I am modifying the Mage_Adminhtml_Block_Sales_Invoice_Grid::_prepareCollection method so that it first fetches customer referred by the logged in admin. Then it will fetch orders from these customer(s) - ideally only the order id's - Then join this collection to sales/order_invoice_grid, to get invoices to be listed for this admin.
Based on the last answer and using these docs, following are 3 ways I have tried joining this information: (Code Sample 1)
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('*'));
$collection->joinTable('sales/invoice_grid', 'order_id=main_table.entity_id', array('*'));
When I do the above, I see the following error:
A joint field with this alias (0) is already declared.
#0 /var/www/magento/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php(706): Mage::exception('Mage_Eav', 'A joint field w...')
#1 /var/www/magento/app/code/local/Myproject/Adminhtml/Block/Sales/Invoice/Grid.php(41): Mage_Eav_Model_Entity_Collection_Abstract->joinTable('sales/invoice_g...', 'order_id=main_t...', Array)
#2 /var/www/magento/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php(576): Myproject_Adminhtml_Block_Sales_Invoice_Grid->_prepareCollection()
#3 /var/www/magento/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php(582): Mage_Adminhtml_Block_Widget_Grid->_prepareGrid()
If I remove the second call to joinTable, the above code works, but it is not what I want.
The other method I tried is with this code:
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('entity_id as order_entity_id'));
$collection->joinTable('sales/invoice_grid', 'order_id=main_table.entity_id', array('*'));
Here the error appears in the second line, where I am actually trying to alias the field order.entity_id so that it does not conflict with invoice tables entity_id. However that produces an error like:
Item (Mage_Customer_Model_Customer)
with the same id "1" already exist
I only need order id's so that I can get related invoices, which suggests that I can also use joinField function, which I tried as follows:
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->joinField('order_entity_id', 'sales/order_grid', 'entity_id', 'customer_id=entity_id' , null, 'left');
But it gives me the following error:
Item (Mage_Customer_Model_Customer) with the same id "1" already exist
I am looking for a solution that joins customer->invoices.
By pre-filter I mean that data listed in the grid is filtered even before anything is presented in the grid.
Ok, now my code looks like:
$collection =
Mage::getResourceModel('customer/customer_collection');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('entity_id' => 'order_entity_id'));
And the error that I get is:
SELECT `e`.*, `sales_flat_order_grid`.`order_entity_id` AS `entity_id` FROM `customer_entity` AS `e`
INNER JOIN `sales_flat_order_grid` ON (sales_flat_order_grid.customer_id=e.entity_id) WHERE (e.entity_type_id = '1') ORDER BY `e`.`created_at` desc, `e`.`created_at` desc LIMIT 20
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sales_flat_order_grid.order_entity_id' in 'field list'
Here is the total of my test script. To use put it in a file at the Magento root and type it's URL directly in your browser, it is not handled by Magento's controllers. This is a good way of experimenting as it is not as influenced by other modules, page layouts, etc.
<pre><?php
require 'app/Mage.php';
Mage::app();
$collection = Mage::getResourceModel('customer/customer_collection');
$collection->getSelect()->reset('columns');
$collection->joinTable('sales/order_grid', 'customer_id=entity_id', array('order_entity_id' => 'entity_id'));
$collection->joinTable('sales/invoice_grid', 'order_id=order_entity_id', array('*'));
foreach ($collection as $invoice)
print_r($invoice->debug());
?></pre>
As with your previous question I choose to reset the initial columns because I don't believe in giving the database more work than necessary. However it's not essential, the test still succeeds without it.
If this doesn't work in your installation then we need to consider what that outside influence could be.
The error "A joint field with this alias (0) is already declared." occurs because it uses array keys as aliases. Since you have two joinTable() calls, each with an array, it is trying to use the zero-based index of both and obviously having a conflict.
So instead of
array('entity_id as order_entity_id')
try
array('entity_id' => 'order_entity_id')
to avoid the conflict.
I finally did achieve this by going from invoice->order->customer as 'Anda B' suggested. I am just pasting my solution here as a reference, but will be using this solution from clockworkgeek, since it seems much cleaner. And my solution still needs to be made cleaner by getting the 'id' of eav_attribute (agent_id) from the database at runtime, instead of hard coding it, as pasted here:
class Myproject_Adminhtml_Block_Sales_Invoice_Grid extends Mage_Adminhtml_Block_Sales_Invoice_Grid
{
const AGENT_ID_ATTRIBUTE_ID = 118;
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
$collection->join('order_grid', 'order_id = order_grid.entity_id', array ('order_entity_id' => 'order_grid.entity_id'));
$collection->getSelect()->join( 'customer_entity', 'customer_id = customer_entity.entity_id', array('customer_entity_id' => 'entity_id', 'email'));
$collection->getSelect()->joinLeft( 'customer_entity_int', 'customer_entity_int.entity_id = customer_entity.entity_id AND attribute_id = ' . Myproject_Adminhtml_Block_Sales_Invoice_Grid::AGENT_ID_ATTRIBUTE_ID,
array('attribute_entity_id' => 'customer_entity_int.entity_id', 'attribute_id' , 'value'));
//Apply Desired Data Filters here
$this->setCollection($collection);
return $collection;
It gives you this error "Item (Mage_Customer_Model_Customer) with the same id "1" already exist" because a customer can have multiple orders and so could have two or more entries with the same customer id - you are creating a collection of customers and you must have unique entries in the collection.
You have to start from invoices and join them with the customers.
The simplest way I found out from the magento forum ,
In the
protected function _prepareCollection()
{
$collection = Mage::getResourceModel('customer/customer_collection')
We can use custom queries as
$collection->getSelect()->columns(array('filename' => new Zend_Db_Expr ("(SELECT filename FROM cat WHERE customer_id =e.entity_id)")));
and it works
$this->setCollection($collection);
var_dump($collection);

Resources