FULL OUTER JOIN on a Many-to-Many with LINQ Entity Framework - sql

I have a many-to-many relationship of products (p) and materials (m) and the products2materials table (p2m) as the many-to-many link.
I need to get
- all products that have materials assigned,
- all products with no materials assigned,
- and all materials with no products assigned.
Basically a union of what is there.
However, since this will be a data filter, I need to filter out products and/or materials that do not match the search criteria (e.g. all products that start with "A", etc.).
How do I do this in LINQ-to-EF 4.1?
Many thanks!

The following should do the job:
from m in context.Materials //m has to be the first
from p in context.Products
where !p.Select(p1 => p1.Material).Contains(m) || p.Material == null || p.Material == m
For performance it would probably be better the following:
var a = from p in context.Products select p.Material;
var b = from m in context.Materials //m has to be the first
from p in context.Products
where a.Contains(m) || p.Material == null || p.Material == m

Linq doesn't offer full outer join operation directly so your best choice is to try separate left and right join L2E queries and union them to single result set.
I would try something like (not tested):
var query = (from p in context.Products
from m in p.Materials
select new { p, m })
.Union(
from m in context.Materials
from p in m.Products
select new { p, m })
...
Perhaps you will have to use DefaultIfEmpty to enforce outer joins.

From yours description it looks like you actually need:
All Products
All unused Materials
Do you need it as a single IQueryable (if yes which fields do you need) or as 2 IQueryables?

Related

Unable to convert SQL Query to LINQ Query for Left Outer Join

Problem Statement:
I'm trying to convert one of my Sql to linq query, but I'm unable to get the desired output which i need. Can anyone suggest me what i should do?
SQL Query:
SELECT AssetTagging.AssetID, AssetTagging.AssetDescription, [Return].RequestStatus
FROM AssetTagging
LEFT OUTER JOIN [Return] ON AssetTagging.AssetID = [Return].AssetID
LEFT OUTER JOIN Issue ON AssetTagging.AssetID = Issue.AssetID
WHERE (Issue.AssetID IS NULL) OR ([Return].RequestStatus = 'Approved')
Linq Query I'm using:
var result = (from at in db.AssetTagging.AsEnumerable()
join r in db.Return on at.AssetID equals r.AssetID
orderby at.AssetID
where !db.Issue.Any(issue=>issue.AssetID==at.AssetID) || r.RequestStatus=="Approved"
select new globalTestModel
{
model1=at
}).ToList();
//I know that in Linq query I'm using Inner join instead of Left Join,but i'm getting error if i use left join instead of inner join?
What am I doing wrong??
Any suggestion to get desired query like Sql in Linq?
Asset Tag table:
Issue table:
Return table:
Desired Output :
You need to remove .AsEnumerable(), because you want your query to be translated to sql. Right now it would be using linq-to-objects and if you are using a left join with linq-to-object you need to check for null reference exceptions. rt could be null, so rt.RequestStatus would throw an exception.
*I believe rt should be r in your example
You can't project to an existing entity, so you need to change your select to:
select new PocoClass
{
model1=at
}
//New class definition
public PocoClass
{
public AssetTagging model1 { get; set; }
}
You need to do like this:
var result = from at in db.AssetTagging
join r in db.Returns on at.AssetID equals r.AssetID into a
from returns into a.DefaultIfEmpty()
join i in db.Issues on at.AssetID equals I.AssetID into b
from issues into b.DefaultIfEmpty()
where issues.AssetID != null || returns.RequestStatus == "Approved"
select new
{
AssetID = at.AssetID,
AssetDescription = at.AssetDescription,
Status = returns != null ? returns.RequestStatus : null
}.ToList();
try like following:
from at in db.AssetTagging
join r in db.Return on at.AssetID equals r.AssetID into res1
from atr in res1.DefaultIfEmpty()
join i in db.Issues on i.AssetID==at.AssetID into res2
from obj in res2.DefaultIfEmpty()
select at
where i.AssetID == null || r.RequestStatus equals "Approved"
Just make two times left outer join and then do filter on where condition.
Also have first look at this msdn article about left outer join using linq.
Try the following I am assuming that you still want the cases where r is null unless r is not null and request status = approved.
You have to check to verify r!=null before checking the request status and you will still need to include when r is null to get the complete result set. I haven't tested this, but this should put you in the right direction.
Good luck.
var result = (from at in db.AssetTagging
join r in db.Return.DefaultIfEmpty()
on at.AssetID equals r.AssetID
join i in db.Issue.DefaultIfEmpty()
on at.AssetID equals i.AssetID
where
(r == null || (r!=null && r.RequestStatus == "Approved"))
|| i == null
select new {
at.AssetID,
at.AssetDescription,
IssueID = (i!=null) ? i.IssueID : null),
ReturnID = (r!=null) ? r.ReturnID: null),
ReturnStatus = (r!=null)
? r.ReturnStatus
: null}).ToList();
I know this isn't exactly what you've asked for, but it might be useful anyway.
If you have access to the database to execute SQL queries, I would suggest creating a view. You can then drop the view into your DBML file the same way as you would with a table, and have much simpler Linq expressions in your C# code.
CREATE VIEW [Asset_Issue_Return_Joined] AS
SELECT AssetTagging.AssetID, AssetTagging.AssetDescription, [Return].RequestStatus
FROM AssetTagging
LEFT OUTER JOIN [Return] ON AssetTagging.AssetID = [Return].AssetID
LEFT OUTER JOIN Issue ON AssetTagging.AssetID = Issue.AssetID
WHERE (Issue.AssetID IS NULL) OR ([Return].RequestStatus = 'Approved')
Here is the complete query
var result = (from assetTagging in db.AssetTagging
join return0 in db.Return on assetTagging.AssetID equals return0.AssetID into returns
from return0 in returns.DefaultIfEmpty()
join issue in db.Issue on assetTagging.AssetID equals issue.AssetID into issues
from issue in issues.DefaultIfEmpty()
where issue.AssetID == null || return0.RequestStatus == "Approved"
select new
{
assetTagging.AssetID,
assetTagging.AssetDescription,
return0.RequestStatus
}).ToList();

How can i do this SQL in Linq? (Left outer join w/ dates)

My LINQ isnt the best, neither is my SQL but im trying to do something like this in LINQ (its kind of in pseudo-code)
select * from CarePlan c
-- only newest Referral based on r.Date (if more than one exists)
left outer join Referral r on r.CarePlanId = c.CarePlanId
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
The data looks like this:
A Patient can have a bunch of careplans
each careplan can have 0 to n referrals (I want the newest referral per careplan - if any exist)
Would like to return a list of careplans for each patient (whether or not they have a referral or not, if it has more than one referral - grab the newest one)
Thanks for any help guys
In LINQ you use the DefaultIfEmpty to achieve a left outer join - examples at http://msdn.microsoft.com/en-us/library/bb397895.aspx
Assuming that the referrals are not a (potentially empty) collection on the care plans so you're joining two collections together ...
Your query it would be something like:
Get the latest referral per Care Plan:
var latestReferrals = from r in referrals
group r by r.CarePlanId into lr
select new { CarePlanId = lr.Key, LatestReferral = lr.OrderByDescending(lrd => lrd.Date).First()};
Find the combined details:
var q = from c in CarePlan
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
join lr in latestReferrals on c.CarePlanId equals lr.CarePlanId into gj
from subReferral in gj.DefaultIfEmpty()
select new { CarePlanId = c.CarePlanId, LatestReferral = (subReferral == null) ? null : subReferral.LatestReferral };
Depending on whether you want many referral properties or just a few you may or may not want the whole Referral object in the second part or just extract the relevant properties.
You may be able to combine these into a single query but for readability it may be easier to keep them separate. If there is a combined solution you should also compare performance of the two approaches.
EDIT: see comment for joining patients/other tables from care plans
If Patient is joined from Referral (as per comment) then its more complex because you're doing several left outer joins. So switching to the slightly more concise syntax:
var combined = from c in carePlans
where c.PatientId = 'E4A1DA8B-F74D-4417-8AC7-B466E3B3FFD0'
from lr in latestReferral.Where(r => r.CarePlanId == c.CarePlanId).DefaultIfEmpty()
from p in patients.Where(patient => patient.PatientId == ((lr != null) ? lr.LatestReferral.PatientId : -1)).DefaultIfEmpty()
select new { c.CarePlanId, PatientName = (p == null) ? "no patient" : p.PatientName, LatestReferral = (lr == null) ? null : lr.LatestReferral };

How to get in prestashop filtered products by its attribute

I have no experience in prestashop.
I'm trying to get all products which matches to some value of its attribute.
[edit]
Now I get all products by using Product::getProducts. After that I filter result array by array_filter. I'm wondering about method to retrieve that filtered products directly, where filtering is doing in database. Eg. passing a function or string filter like
Product::getProductsBy( 'product_id > 10')
The short answer is that no there isn't anything built in that can do this, however, IF I'm understanding correctly, then you want a list of products that have a particular attribute value in a suitable format to output using the product listing theme template.
I'll assume that you start from a drop-down list of attribute values within a group and as such we can ignore attribute grouping completely. For example you may have an attribute group "Size" (id_attribute_group = 1) with selectable values "M" and "L", these latter corresponding to entries in the XX_attribute table.
In the XX_attribute table you would have
id_attribute | id_attribute_group | color
1 | 1 | #000000
2 | 1 | #000000
In the XX_attribute_lang table you would have
id_attribute | id_lang | name
1 | 1 | M
2 | 1 | L
Assuming that you don't care about categories etc. - to get all the products available in size "L" you could then just use a simple query such as:
SELECT pa.`id_product` FROM `XX_product_attribute` pa
LEFT JOIN `XX_product_attribute_combination` pac ON pa.`id_product_attribute` = pac.`id_product_attribute`
WHERE pac.`id_attribute` = '2'
You then have to bascially replicate the functionality used internally by Prestashop to retrieve the product details for an array of product_ids obtained above to pass to the product listing template. Getting only the product ids at this stage is probably the best approach since you need to post-process the product data anyway for language, currency, tax etc.
To get the detailed product data from an array of ids built from the above or a similar filtering query (e.g. stored in an array called $product_ids) you would have to do something like:
global $cookie;
$id_lang = $cookie->id_lang;
$product_list = array();
if (count($product_ids))
{
$sql = '
SELECT p.*, pa.`id_product_attribute`, pl.`description`, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, i.`id_image`, il.`legend`, m.`name` AS manufacturer_name, tl.`name` AS tax_name, t.`rate`, cl.`name` AS category_default, DATEDIFF(p.`date_add`, DATE_SUB(NOW(), INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY)) > 0 AS new,
(p.`price` * IF(t.`rate`,((100 + (t.`rate`))/100),1)) AS orderprice
FROM `'._DB_PREFIX_.'category_product` cp
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = cp.`id_product`
LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (p.`id_product` = pa.`id_product` AND default_on = 1)
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (p.`id_category_default` = cl.`id_category` AND cl.`id_lang` = '.(int)($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = '.(int)($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product` AND i.`cover` = 1)
LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group`
AND tr.`id_country` = '.(int)Country::getDefaultCountryId().'
AND tr.`id_state` = 0)
LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.(int)($id_lang).')
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer`
WHERE p.`active` = 1' : '').'
'.($id_supplier ? 'AND p.id_supplier = '.(int)($id_supplier) : '').'
AND p.`id_product` IN(' . implode(',', $product_ids) . ')';
$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS($sql);
/* Modify SQL result */
$product_list = Product::getProductsProperties($id_lang, $result);
}
Obviously I've used IN() to filter the results to our product set, which for a large number of products is going to get tricky, but it gives the general idea! Another issue is that the query above is pretty much lifted out of a similar function elsewhere in Prestashop (apologies if I've mis-edited it anywhere!) and as such may break on upgrade....
As Paul mentioned above, your question is extremely vague and may be difficult to properly answer without additional information. However, I will try to offer a couple potential solutions:
If you're looking to purchase a specific product, such as an iPod, with options for color and size, you can do this using the Attributes & Groups tab and the Combinations Generator in your Catalog.
If you're looking to narrow down a list of products based on parameters such as price, features, color, etc. (think of how you shop on Amazon.com, for example), you can configure this using the built-in Layered Navigation module.
If neither of these address your question, please post additional information to help us better understand what you are asking.
Source: I am PrestaShop's Community Manager

translating a query on collections from SQL to nhibernate

I have a database schema, where I have a Product, Category, CategoryFeature, and an ProductCategoryFeatureValue.
The Model is mapped using Fluent NHibernate, but basically is as follows.
Product
-------
ID
Title
Category
--------
ID
Title
CategoryFeature
---------------
ID
CategoryID
Title
ProductCategoryFeatureValue
---------------
ID
ProductID
CategoryFeatureID
_______________________
Category [one] <-> [many] CategoryFeature
Product [many] <-> [many] ProductCategoryFeatureValue
Basically, the features available to a product are listed in the ProductCategoryFeatureValue table, which is the 'middle-link' for the many-to-many collection.
I need to create a query, where i can find all products, which have ALL the features selected by the user.
Example, doing a search for two features with ids 643229 & 667811 in SQL terms, I would do something like this:
SELECT * FROM Product
JOIN ProductCategoryFeatureValue AS feature1 ON Product.id = feature1.ProductID AND feature1.categoryfeatureid = 643229
JOIN productcategoryfeaturevalue AS feature2 ON Product.id = feature2.ProductID AND feature2.categoryfeatureid = 667811
Another query which I could do is this:
SELECT * FROM product WHERE
((SELECT id FROM productcategoryfeaturevalue AS feature1 WHERE feature1.ItemGroupID = product.id AND feature1.categoryFeatureID = 643229 LIMIT 1) IS NOT NULL)
AND
((SELECT id FROM productcategoryfeaturevalue AS feature2 WHERE feature2.ItemGroupID = product.id AND feature2.categoryFeatureID = 667811 LIMIT 1) IS NOT NULL)
Both have been tested and work well. However, I cannot seem to reproduce them using NHibernate. Any ideas?
Thanks!
I believe you want something like this in SQL
Select *
from Products p
where p.id in (
select fv.ProductId
from ProductCategoryFeatureValue fv
where fv.CategoryFeatureID in (643229,643230)
group by fv.ProductId
having count(*)=#NumberOfDistinctFeaturesSelected
)
That will stop you having to JOIN to the ProductCategoryFeatureValue table multiple times for every feature selected by the user. At the very least your going to get a nicer query plan. If you don't like the IN clause you could also use a temp table instead.
In terms of translating this into NHibernate it doesn't support any HAVING clause logic in the Criteria API but it is supported using HQL.
HQL Examples
var results = session.CreateQuery("from Product p where p.Id in (
select fv.Product.Id
from ProductCategoryFeatureValue fv
where fv.CategoryFeature.Id in :featureids
group by fv.Product.Id
having count(fv)=:features
)")
.SetParameter("featureids", arrayOfFeatureIds)
.SetParameter("features", arrayOfFeatureIds.Count)
.List<Product>();
Not 100% sure from the question exactly what your mappings are but this may be close to what you need
object[] featureIds = new object[2];
featureIds[0] = 643229;
featureIds[1] = 667811;
ICriteria criteria = base.CreateCriteria(typeof(Product));
criteria.CreateAlias("ProductCategoryFeatureValueList",
"ProductCategoryFeatureValue", JoinType.InnerJoin);
criteria.CreateAlias("ProductCategoryFeatureValue.CategoryFeatureID",
"CategoryFeature", JoinType.InnerJoin);
criteria.Add(Expression.In("CategoryFeature.ID", featureIds));
If the "Expression.In" doesn;t quite do what you are after you could just do a quick loop adding
criteria.Add(Expression.Eq("CategoryFeature.ID", featureIds[i]));
I think your biggest problem is adding a condition on a join. I haven't tried it yet, but have been looking forward to the feature of NH 3.+ that lets you add a criteria join.
CreateAlias(string associationPath, string alias, JoinType joinType, ICriterion withClause) CreateCriteria(string associationPath, string alias, JoinType joinType, ICriterion withClause)

linq2sql left join with "multiselect"

I'm trying to achieve following by linq2sql, but not successful.
I've Member and Reference tables. DB is design in such a manner that Member can have multiple (>=0) References. What I want as a result of query is, list (rows) of members, where all references of the member are "collected" in one column.
What I had achieved is following query, but for this one there exist a row for each Reference.
var refs = (from m in db.Members
join
r in db.References on m.PID equals r.PID into g
from o in g.DefaultIfEmpty()
select new
{
member = m,
name = (o == null ? "" : o.NameSurname)
});
I feel I need to insert SelectMany somewher :)
Could you please give hints on achieving the goal?
var refs = (from m in db.Members
select new
{
member = m,
name = String.Join(",",(from r in db.References on m.PID equals r.PID into g
from o in g.DefaultIfEmpty() select o.NameSurname).toArray())
}).Distinct();
This is untested but I think this is what your looking for. It should grab your member and have all the references joined into name. If it doesn't work let me know and I will look into it furthur.
Do you mean like this:
var refs = from m in db.Members
join r in db.References on m.PID equals r.PID into j
from o in j.DefaultIfEmpty()
select new { Member = m, Reference = o } into results
group result by result.Member into g
select g;
That would group all your results by member (the key of the group) and all the references associated with them (results matching that key).
Although you'd probably be better just setting the relationships up in the ORM then you can just reference the (presumably named) member.References property like so:
foreach( var member in db.Members )
{
var allReferences = member.References;
}