Magento: Get Collection of Order Items for a product collection filtered by an attribute - sql

I'm working on developing a category roll-up report for a Magento (1.6) store.
To that end, I want to get an Order Item collection for a subset of products - those product whose unique category id (that's a Magento product attribute that I created) match a particular value.
I can get the relevant result set by basing the collection on catalog/product.
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToFilter('unique_category_id', '75')
->joinTable('sales/order_item', 'product_id=entity_id', array('price'=>'price','qty_ordered' => 'qty_ordered'));
Magento doesn't like it, since there are duplicate entries for the same product id.
How do I craft the code to get this result set based on Order Items? Joining in the product collection filtered by an attribute is eluding me. This code isn't doing the trick, since it assumes that attribute is on the Order Item, and not the Product.
$collection = Mage::getModel('sales/order_item')
->getCollection()
->join('catalog/product', 'entity_id=product_id')
->addAttributeToFilter('unique_category_id', '75');
Any help is appreciated.

The only way to make cross entity selects work cleanly and efficiently is by building the SQL with the collections select object.
$attributeCode = 'unique_category_id';
$alias = $attributeCode.'_table';
$attribute = Mage::getSingleton('eav/config')
->getAttribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
$collection = Mage::getResourceModel('sales/order_item_collection');
$select = $collection->getSelect()->join(
array($alias => $attribute->getBackendTable()),
"main_table.product_id = $alias.entity_id AND $alias.attribute_id={$attribute->getId()}",
array($attributeCode => 'value')
)
->where("$alias.value=?", 75);
This works quite well for me. I tend to skip going the full way of joining the eav_entity_type table, then eav_attribute, then the value table etc for performance reasons. Since the attribute_id is entity specific, that is all that is needed.
Depending on the scope of your attribute you might need to add in the store id, too.

Related

Display Product Combinations in Prestashop Customer View

I am trying to get the product combinations displayed in Prestashop admin Customer detail view in addition to the products that are displayed for the customer.
This seems to be the relevant call from AdminCustomersController.php in public form renderForm():
$products = $customer->getBoughtProducts();
Then in the Customer class I found the method:
public function getBoughtProducts()
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT * FROM `'._DB_PREFIX_.'orders` o
LEFT JOIN `'._DB_PREFIX_.'order_detail` od ON o.id_order = od.id_order
WHERE o.valid = 1 AND o.`id_customer` = '.(int)$this->id);
}
How can I modify this method to show the product combination alongside the product name?
I am using Prestashop version 1.6.0.9.
you can get it using 2 ways:
order_detail table already have field 'product_name' that contains value like 'Product Name - Combination', so you can use $products['product_name'] in that case.
or
if for some reason it is not good for you, same table contains also product_attribute_id field, it is combination id, so:
$combination = new Combination($product['product_attribute_id']);
$attributes = $combination->getAttributesName($id_lang);
var_dump($attributes);
will give you array of attributes that current combination contains.

nHibernate QueryOver and JoinQueryOver

I'm reasonably new to nHibernate and I've been trying to write a query. I cant seem to get it right. I have in my model, a "Product" which contains a ICollection of "Component". I need to find all products that contain any components that have a component reference starting with the letter "G". I have tried this:
var matching = session.QueryOver<Product>()
.JoinQueryOver<Component>(p => p.Components)
.Where(c => c.ComponentReference.StartsWith("G")).List();
However I am getting a compile error saying 'Delegate System.Func>> does not take 1 parameter.
There is an overload on JoinQueryOver where I can pass in an Expression>>
So I would have thought my query would work since ICollection implements IEnumerable.
I have tried various other ways using .Cast and JoinAlias but they just seem unecessarily complicated.
Can anyone point out where I am going wrong?
Thanks in advance
I would suggest to use the subquery, in this case. It could look like this
Product product = null;
Component component = null;
// the subselect, returning the Product.ID
// only if the Component.ComponentReference is like G%
var subQuery = QueryOver.Of<Component>(() => component)
.WhereRestrictionOn(() => component.ComponentReference)
.IsLike("G", MatchMode.Start)
.Select(c => c.Product.ID);
// the root Product
var query = session.QueryOver<Product>(() => product)
.WithSubquery
.WhereProperty(() => product.ID)
.In(subQuery);
// and finally the list
var list = query
.List<Product>();
The resulting SQL would be like this:
SELECT product
FROM product
WHERE productId IN (SELECT ProductId FROM Component WHERE ComponenentReferece LIKE 'G%')
And why to use subquery instead of JOIN? because the join would in this case result in Carthesian product. The set of products returned would be multiplied by all the components starting with G%
The subquery, will result in pure, flat set of Products, so we can correctly use paging over it (.Take() .Skip())

Rails ignores columns from second table when using .select

By example:
r = Model.arel_table
s = SomeOtherModel.arel_table
Model.select(r[:id], s[:othercolumn].as('othercolumn')).
joins(:someothermodel)
Will product the sql:
`SELECT `model`.`id`, `someothermodel`.`othercolumn` AS othercolumn FROM `model` INNER JOIN `someothermodel` ON `model`.`id` = `someothermodel`.`model_id`
Which is correct. However, when the models are loaded, the attribute othercolumn is ignored because it is not an attribute of Model.
It's similar to eager loading and includes, but I don't want all columns, only the one specified so include is no good.
There must be an easy way of getting columns from other models? I'd preferably have the items return as instances of Model than simple arrays/hashes
When you do a select with joins or includes, you will be returned an ActiveRecordRelation. This ActiveRecordRelation is composed of only the objects of the class which you use to call select on. The selected columns from the joined models are added to the objects returned. Because these attributes are not Model's attribute they don't show up when you inspect these objects, and I believe this is the primary reason for confusion.
You could try this out in your rails console:
> result = Model.select(r[:id], s[:othercolumn].as('othercolumn')).joins(:someothermodel)
=> #<ActiveRecord::Relation [#<Model id: 1>]>
# "othercolumn" is not shown in the result but doing the following will yield correct result
> result.first.othercolumn
=> "myothercolumnvalue"

NHibernate: Why does Linq First() force only one item in all child and grandchild collections with FetchMany()

Domain Model
I've got a canonical Domain of a Customer with many Orders, with each Order having many OrderItems:
Customer
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual int Id {get;set;}
public virtual ICollection<Order> Orders {get;set;}
}
Order
public class Order
{
public Order()
{
Items = new HashSet<OrderItem>();
}
public virtual int Id {get;set;}
public virtual Customer Customer {get;set;}
}
OrderItems
public class OrderItem
{
public virtual int Id {get;set;}
public virtual Order Order {get;set;}
}
Problem
Whether mapped with FluentNHibernate or hbm files, I run two separate queries, that are identical in their Fetch() syntax, with the exception of one including the .First() extension method.
Returns expected results:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).ToList()[0];
Returns only a single item in each collection:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).First();
I think I understand what's going on here, which is that the .First() method is being applied to each of the preceding statements, rather than just to the initial .Where() clause. This seems incorrect behavior to me, given the fact that First() is returning a Customer.
Edit 2011-06-17
After further research and thinking, I believe that depending on my mapping, there are two outcomes to this Method Chain:
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items);
NOTE: I don't think I can get subselect behavior, since I'm not using HQL.
When the mapping is fetch="join" I should get a cartesian product between the Customer, Order and OrderItem tables.
When the mapping is fetch="select" I should get a query for Customer, and then multiple queries each for Orders and OrderItems.
How this plays out with adding the First() method to the chain is where I lose track of what should be happening.
The SQL Query that get's issued is the traditional left-outer-join query, with select top (#p0) in front.
The First() method is translated into SQL (T-SQL at least) as SELECT TOP 1 .... Combined with your join fetching, this will return a single row, containing one customer, one order for that customer and one item for the order. You might consider this a bug in Linq2NHibernate, but as join fetching is rare (and I think you're actually hurting your performance pulling the same Customer and Order field values across the network as part of the row for each Item) I doubt the team will fix it.
What you want is a single Customer, then all Orders for that customer and all Items for all those Orders. That happens by letting NHibernate run SQL that will pull one full Customer record (which will be a row for each Order Line) and construct the Customer object graph. Turning the Enumerable into a List and then getting the first element works, but the following will be slightly faster:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();
the AsEnumerable() function forces evaluation of the IQueryable created by Query and modified with the other methods, spitting out an in-memory Enumerable, without slurping it into a concrete List (NHibernate can, if it wishes, simply pull enough info out of the DataReader to create one full top-level instance). Now, the First() method is no longer applied to the IQueryable to be translated to SQL, but it is instead applied to an in-memory Enumerable of the object graphs, which after NHibernate has done its thing, and given your Where clause, should be zero or one Customer record with a hydrated Orders collection.
Like I said, I think you're hurting yourself using join fetching. Each row contains the data for the Customer and the data for the Order, joined to each distinct Line. That is a LOT of redundant data, which I think will cost you more than even an N+1 query strategy.
The best way I can think of to handle this is one query per object to retrieve that object's children. It would look like this:
var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();
customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();
foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();
This requires a query for each Order, plus two at the Customer level, and will return no duplicate data. This will perform far better than a single query returning a row containing every field of the Customer and Order along with each Item, and also better than sending a query per Item plus a query per Order plus a query for the Customer.
I'd like to update the answer with my found so that could help anybody else with the same problem.
Since you are querying the entity base on their ID, you can use .Single instead of .First or .AsEnumerable().First():
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).Single();
This will generate a normal SQL query with where clause and without the TOP 1.
In other situation, if the result has more than one Customer, exception will be thrown so it won't help if you really need the first item of a series based on condition. You have to use 2 queries, one for the first Customer and let the lazy load do the second one.

Can I get all the fields in an item (in Sitecore)?

I'm trying to write a sql query to get all the fields in a given item in Sitecore.
To say I am stuck is putting it mildly.
I'm guessing I have to do some self joining on the fields table, but I'm getting myself in knots.
Anyone have any ideas?
In none of the cases you should ever try to query the Sitecore database yourself. The database changes over time and this would break your code. Rather, use the Item.Fields. This is a collection which contains all the necessary fields. If you want to make sure that all the fields are loaded(really loaded, not lazy loaded), than you can use Item.Fields.ReadAll().
Edit: Also, keep in mind that querying doesn't allow you to construct an Item, so you miss the behavior of default values and do not use the intelligent Sitecore caching at all.
Try to call Sitecore.Context.Item.Fields.ReadAll() before looking up a field.
First Attempt, but does not return all fields
SELECT I2.Name FROM
Items AS I
JOIN UnversionedFields AS UF ON I.ID = UF.ItemId
JOIN VersionedFields AS V ON I.ID = V.ItemId
JOIN SharedFields AS S ON I.ID = S.ItemId
JOIN Items AS I2 ON I2.ID = UF.FieldId OR I2.ID=V.FieldId OR I2.ID = S.FieldId
WHERE I.ID = '110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9'
GROUP BY I2.Name
By calling item.Fields you get the item fields that you have designated in your templates as well as the Sitecore standard fields that exist on all items. Use the code below if you only want the fields that you have defined in your templates. Of course, this assumes your field names do not start with "__"
// Get Fields directly from the Item
List<string> fieldNames = new List<string>();
item.Fields.ReadAll();
FieldCollection fieldCollection = item.Fields;
foreach (Field field in fieldCollection)
{
//Use the following check if you do not want
//the Sitecore Standard Fields
if (!field.Name.StartsWith("__"))
{
fieldNames.Add(field.Name);
}
}