I have a left outer join (below) returning results as expected. I need to limit the results from the 'right' table to the 'first' hit. Can I do that somehow? Currently, I get a result for every record in both tables, I only want to see one result from the table on the left (items) no matter how many results I have in the right table (photos).
var query = from i in db.items join p in db.photos on i.id equals p.item_id into tempPhoto from tp in tempPhoto.DefaultIfEmpty() orderby i.date descending select new { itemName = i.name, itemID = i.id, id = i.id, photoID = tp.PhotoID.ToString() }; GridView1.DataSource = query; GridView1.DataBind();
Left joins can increase the number of rows in the left table if there are multiple matches in the right table.
You can use LINQ to perform a left outer join by calling the DefaultIfEmpty method on the results of a group join.
There are two line items for ID 1003 in the second table, so the result of the join will be 2 line items. So, if your secondary tables have more than one row for the key you're joining with, then the result of the join will be multiple rows, resulting in more rows than the left table.
The SQL LEFT JOIN returns all rows from the left table, even if there are no matches in the right table. This means that if the ON clause matches 0 (zero) records in the right table; the join will still return a row in the result, but with NULL in each column from the right table.
This will do the job for you.
from i in db.items let p = db.photos.Where(p2 => i.id == p2.item_id).FirstOrDefault() orderby i.date descending select new { itemName = i.name, itemID = i.id, id = i.id, photoID = p == null ? null : p.PhotoID.ToString(); }
I got this sql when I generated it against my own model (and without the name and second id columns in the projection).
SELECT [t0].[Id] AS [Id], CONVERT(NVarChar,( SELECT [t2].[PhotoId] FROM ( SELECT TOP (1) [t1].[PhotoId] FROM [dbo].[Photos] AS [t1] WHERE [t1].[Item_Id] = ([t0].[Id]) ) AS [t2] )) AS [PhotoId] FROM [dbo].[Items] AS [t0] ORDER BY [t0].[Id] DESC
When I asked for the plan, it showed that the subquery is implemented by this join:
<RelOp LogicalOp="Left Outer Join" PhysicalOp="Nested Loops">
What you want to do is group the table. The best way to do this is:
var query = from i in db.items join p in (from p in db.photos group p by p.item_id into gp where gp.Count() > 0 select new { item_id = g.Key, Photo = g.First() }) on i.id equals p.item_id into tempPhoto from tp in tempPhoto.DefaultIfEmpty() orderby i.date descending select new { itemName = i.name, itemID = i.id, id = i.id, photoID = tp.Photo.PhotoID.ToString() };
Edit: This is Amy B speaking. I'm only doing this because Nick asked me to. Nick, please modify or remove this section as you feel is appropriate.
The SQL generated is quite large. The int 0 (to be compared with the count) is passed in via parameter.
SELECT [t0].X AS [id], CONVERT(NVarChar(MAX),( SELECT [t6].Y FROM ( SELECT TOP (1) [t5].Y FROM [dbo].[Photos] AS [t5] WHERE (([t4].Y IS NULL) AND ([t5].Y IS NULL)) OR (([t4].Y IS NOT NULL) AND ([t5].Y IS NOT NULL) AND ([t4].Y = [t5].Y)) ) AS [t6] )) AS [PhotoId] FROM [dbo].[Items] AS [t0] CROSS APPLY (( SELECT NULL AS [EMPTY] ) AS [t1] OUTER APPLY ( SELECT [t3].Y FROM ( SELECT COUNT(*) AS [value], [t2].Y FROM [dbo].[Photos] AS [t2] GROUP BY [t2].Y ) AS [t3] WHERE (([t0].X) = [t3].Y) AND ([t3].[value] > @p0) ) AS [t4]) ORDER BY [t0].Z DESC
The execution plan reveals three left joins. At least one is trivial and should not be counted (it brings in the zero). There is enough complexity here that I cannot clearly point to any problem for efficiency. It might run great.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With