Group By Having and Count as LINQ query with multiply nested tables - sql

I have the following SQL query to return all Customers who have no OrderLines with no Parts assigned - i.e. I only want the customers within which every order line of every order has no parts assigned - (in the actual problem I am dealing with a different domain but have translated to customers/orders to illustrate the problem)
SELECT c.Customer_PK
FROM Customers c
INNER JOIN Orders o
ON c.Customer_PK = o.Customer_FK
LEFT OUTER JOIN OrderLines l
ON o.Order_PK = l.Order_FK
LEFT OUTER JOIN Parts p
ON l.OrderLine_PK = p.OrderLine_FK
GROUP BY c.Customer_PK
HAVING COUNT(p.Part_PK) = 0
The best I have come up with in LINQ is as follows:
Dim qry =
(From c In context.Customers
Select New With { c.Customer_PK,
.CountParts =
(From o In c.Orders
From l In o.OrderLines
Select l.Parts.Count).DefaultIfEmpty.Sum})
qry = (From grp In qry
Where grp.CountParts = 0
Select grp.Customer_PK)
This works but generates less than optimal SQL - it is doing a subquery for Count on each row of the customers query rather than using Group By and Having. I tried making the LINQ Group By syntax work but it kept putting the filter as a WHERE not a HAVING clause.
Any ideas?
Edit in response to Answers below:
I am accepting JamieSee's answer as it addresses the stated problem, even though it does not produce the GROUP BY HAVING query I originally had.
Thanks Peter and Nick for your input on this. I am a VB developer so I have had a crack translating your code to VB, this is the closest I got to but it does not quite produce the desired output:
Dim qry = From c In context.Customers
Group Join o In context.Orders On c.Customer_PK Equals o.Customer_FK
Into joinedOrders = Group
From jo In joinedOrders.DefaultIfEmpty
Group Join l In context.OrderLines On jo.Order_PK Equals l.Order_FK
Into joinedLines = Group
From jl In joinedLines.DefaultIfEmpty
Group c By Key = New With {c.Customer_PK, jl} Into grp = Group
Where Key.jl Is Nothing OrElse Not Key.jl.Parts.Any
Select c.Customer_PK
The problem I had is that I have to push "jl" into the Group By "Key" so I can reference it from the Where clause, otherwise the compiler cannot see that variable or any of the other variables appearing before the Group By clause.
With the filter as specified I get all customers where at least one order has lines with no parts rather than only customers with no parts in any order.

Given that you don't care about the counts, only the resulting customers, consider the folllowing restatement of the problem:
Identify all Customers who do not have any Orders that have Lines with Parts.
This yields:
var customersWithoutParts = from c in Context.Customers
where !(from o in Context.Orders
from l in o.Lines
from p in l.Parts
select o.Customer_FK).Contains(c.Customer_PK)
select c.Customer_PK;
This should yield emitted SQL that is roughly equivalent to the following:
SELECT c.Customer_PK
FROM Customers AS c
WHERE (NOT EXISTS
(SELECT o.Cusomer_FK
FROM Orders AS o INNER JOIN
OrderLines AS l ON o.Order_PK = l.Order_FK INNER JOIN
Parts AS p ON l.OrderLine_PK = p.OrderLine_FK
WHERE (o.Customer_FK = c.Customer_PK)))
To get the SQL you were trying to reproduce, I'd start by trying the following:
var customersWithoutParts = from c in Context.Customers
from o in c.Orders.DefaultIfEmpty()
from l in o.Lines.DefaultIfEmpty()
join part in Context.Parts on part.OrderLine_FK equals l.OrderLine_PK into joinedParts
where joinedParts.Count() == 0
select c.Customer_PK;
Note that in VB the join here would be replaced by Group Join.

Just a tipp bocuse hard to create a query without the generated modells (C#):
from o in dc.Orders
join jOrderLines in dc.OrderLines on o.Order_PK equals jOrderLines.Order_FK into joinedOrderlines
from l in joinedOrderLines.DefaultIfEmpty()
group o by o.Customer_FK into g
where l == null || l.Count(x => x.Parts) == 0
select g.Key

What about something like this:
var qry = from c in db.Customers
join o in db.Orders.Where(x => x.Customer_FK == c.Customer_PK)
join l in db.OrderLines.Where(x => x.Order_FK = o.Order_PK).DefaultIfEmpty()
join p in db.Parts.Where(x => x.OrderLine_FK = l.OrderLine_PK).DefaultIfEmpty()
group c by new
{
c.Customer_PK
} into g
where g.Count(p => p.Part_PK != null) == 0
select new
{
Customer_PK = g.Key.Customer_PK
};

Related

How to subtract from another table in SQL

SELECT
COUNT(ca.Plate) as 'OccupiedElectricSlots'
FROM cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
WHERE ps.ElectricOutlet = 1
GROUP BY hs.HouseName, C.CityName
SELECT
MAX(Ps.SlotNumber) as 'ParkingSlotTotal'
,MAX(PS.SlotNumber) - Count(ca.Plate) as 'FreeSlots'
,SUM(CAST(PS.ElectricOutlet AS INT)) as 'ElectricOutlet'
,Hs.HouseName
,C.CityName
FROM Cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
How can I subtract the first tables numbers on the second one?
I want to see how many free slots that have electric outlet.
Like this Column ElectricOutlet - OccupiedElectricSlots = result
I'm quite new at SQL, but I have tried to outer apply (don't fully understand it), and I tried to join them both tables togheter. Tried different where conditions but I'm stuck atm.
Your queries are almost identical as far as I can see. You can change your first query to:
SELECT COUNT(CASE WHEN ps.ElectricOutlet = 1 THEN ca.Plate END) as 'OccupiedElectricSlots'
FROM cities C
JOIN ParkingHouses HS on C.Id = hs.CityId
JOIN ParkingSlots PS on HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca on PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
I.e., instead of filtering on ps.ElectricOutlet you just ignore those rows in COUNT. Now you can just:
SELECT
[...]
,SUM(CAST(PS.ElectricOutlet AS INT)) - COUNT(CASE WHEN ...) AS result
[...]
FROM Cities C
JOIN ParkingHouses HS
ON C.Id = hs.CityId
JOIN ParkingSlots PS
ON HS.Id = ps.ParkingHouseId
LEFT JOIN Cars Ca
ON PS.Id = Ca.ParkingSlotsId
GROUP BY hs.HouseName, C.CityName
The MINUS operator is used to subtract the result set obtained by first SELECT query from the result set obtained by second SELECT query.
MINUS compares the data in two tables and returns only the rows of data using the specified columns that exist in the first table but not the second.

Converting Inner Join Grouping SQL into Linq

I have the following SQL statement:
SELECT b.Brand_key, b.BrandName, Sum(po.Quantity) AS Quantity
FROM Brands b
INNER JOIN Products p ON b.Brand_key = p.Brand_fkey
INNER JOIN ProductOrders po ON p.Product_key = po.Product_fkey
GROUP BY b.Brand_key
ORDER BY Quantity DESC, b.BrandName ASC
which works perfectly. Distinct values are returned as expected. I've attempted to turn it into Linq, but I feel like I'm going down a rabbit hole that never ends.
var testModelTwo =
(from b in db.Brands
join p in db.Products on b.brandKey equals p.brandKey
select new { brandKey = b.brandKey, BrandName = b.BrandName, ProductKey = p.productKey }
into groupResultBP
join po in pom on groupResultBP.ProductKey equals po.Product_fkey
select new
{
brandKey = groupResultBP.brandKey,
BrandName = groupResultBP.BrandName,
Quantity = po.Quantity
} into gr
group gr by gr.brandKey into gd
select new { Brand = gd.Key, BrandCounter = gd.Sum(c => c.Quantity) } into sumGroup
select sumGroup).ToList();
This seems to get to the point where I have a key and the sum, but need to do another join to get back the brand item information with sumGroup.
I feel like I've gone round the houses about 100 times and this is much easier than I'm making it! Is what I've done so far correct?

LINQ LEFT JOIN to another LEFT JOIN

I am pretty green with LINQ and brand new to converting LEFT JOINs.
I wrote a query in SQL (which works fine) and I am trying to get it into LINQ. I am missing something, but can't see it. The SQL is:
DECLARE #Version int = 1
SELECT Q.WebFormTemplateQuestionID, Lay.*
FROM WebFormTemplates WFT
INNER JOIN WebFormTemplateQuestions Q ON Q.WebFormTemplateID = WFT.WebFormTemplateID
LEFT JOIN WebFormTemplateLayoutHeaders Hdr ON Hdr.WebFormTemplateID = WFT.WebFormTemplateID and hdr.Version = #Version
LEFT JOIN WebFormTemplateLayouts Lay ON Lay.WebFormTemplateLayoutHeaderID = Hdr.WebFormTemplateLayoutHeaderID AND Lay.WebFormTemplateQuestionID = Q.WebFormTemplateQuestionID
WHERE WFT.WebFormTemplateID = 2
ORDER BY Q.SortOrder
The LINQ I wrote is:
Version = 1
Dim q3 = From WFT In ctx.WebFormTemplates
Join Q In ctx.WebFormTemplateQuestions On Q.WebFormTemplateID Equals WFT.WebFormTemplateID
Group Join Hdr In ctx.WebFormTemplateLayoutHeaders On Hdr.WebFormTemplateID Equals WFT.WebFormTemplateID Into Hdr_join = Group
From Hdr In Hdr_join.Where(Function(x) x.Version = Version).DefaultIfEmpty()
Group Join Lay In ctx.WebFormTemplateLayouts On Lay.WebFormTemplateLayoutHeaderID Equals Hdr.WebFormTemplateLayoutHeaderID And Lay.WebFormTemplateQuestionID Equals Q.WebFormTemplateQuestionID Into Lay_join = Group
From Lay In Lay_join.DefaultIfEmpty()
Where WFT.WebFormTemplateID = 2
With this, I can break the code and in the immediate window get the expected values when I type:
?q3.FirstOrDefault().Hdr.Version
But for the Lay item I need to type:
?q3.FirstOrDefault().Lay.FirstOrDefault().QuestionAlign
to get the value.
Why do I need to call FirstOrDefault the second time? What am I missing here?
When I do a Select, the values are all null for Lay I assume because I don't have another FirstOrDefault someplace. Everything from Hdr, WFT, and Q work just fine. Can anyone explain what is going on?
i have written your equivalent Linq query from the SQL in C#.
Update accordingly for VB.
Version = 1
Dim q3 = (from WFT In ctx.WebFormTemplates
join Q In ctx.WebFormTemplateQuestions on Q.WebFormTemplateID equals WFT.WebFormTemplateID
join Hdr In ctx.WebFormTemplateLayoutHeaders on Hdr.WebFormTemplateID equals WFT.WebFormTemplateID into Hdr_join
from Hdr In Hdr_join.Where(Function(x) x.Version = Version).DefaultIfEmpty()
join Lay In ctx.WebFormTemplateLayouts on new { Lay.WebFormTemplateLayoutHeaderID,Lay.WebFormTemplateQuestionID } equals new {Hdr.WebFormTemplateLayoutHeaderID, Q.WebFormTemplateQuestionID} into Lay_join
from Lay In Lay_join.DefaultIfEmpty()
where WFT.WebFormTemplateID = 2
select new {Q.WebFormTemplateQuestionID, Lay});

Why LINQ Count() returning multiple rows instead one?

I would like to translate the following SQL into LINQ:
select count(p.ID) as NumPosts,
count(t.Trustee_ID)as TrusteePost,
count(pat.ID)as PatientPost,
count(s.ID) as SpecialistPost
from [dbo].[Posts] as p
left join [dbo].[Trusteehips] as t
on p.Autor_ID = t.Trustee_ID
left join [dbo].[Patients] as pat
on p.Autor_ID = pat.ID
left join [dbo].[Specialists] as s
on p.Autor_ID = s.ID
where p.Deleted = 0
I I've tried this:
var res = from p in context.Posts
join t in context.Trusteeships
on p.Autor.ID equals t.Trustee.ID into tGroup
join pat in context.Patients
on p.Autor.ID equals pat.ID into patGroup
join s in context.Specialists
on p.Autor.ID equals s.ID into sGroup
select new NumUserPosts
{
//CountAllPosts = ?
TrusteePost = tGroup.Count(),
PatientPost = patGroup.Count(),
SpecialistPost = sGroup.Count()
};
But result is this:
1 0 0
0 0 1
0 0 1
0 1 0
and etc.
I expect result
TrusteePost PatientPost SpecialistPost
1000 2000 3000
Why when i try to count group return this result?
SQL query is correct. I would like to translate into LINQ.
The query returns 0 or 1 records per joined Trustee, etc. because you outer join by the unique primary key. So a join into (which is a GroupJoin in fluent syntax) produces a group of 0 or 1 records. If you run the generated SQL query and view the raw query result you'd probably understand better what's going on.
The problem is, there is no LINQ equivalent for count(t.Trustee_ID), etc. Therefore it's impossible to do what you want in one query without "hacking".
Hacking it into one query could be done like so:
(from p in context.Posts.Take(1)
select new
{
TrusteePost = context.Posts
.Count(p1 => context.Trusteeships.Any(x => x.ID == p1.Autor.ID)),
PatientPost = context.Posts
.Count(p2 => context.Patients.Any(x => x.ID == p2.Autor.ID)),
SpecialistPost = context.Posts
.Count(p3 => context.Specialists.Any(x => x.ID == p3.Autor.ID))
})
.AsEnumerable()
.Select(x => new NumUserPosts
{
CountAllPosts = x.TrusteePost + x.PatientPost + x.SpecialistPost,
x.TrusteePost,
x.PatientPost,
x.SpecialistPost
}
The SQL query will be much more elaborate than the original SQL (for example, it involves cross joins), but it will probably still perform pretty well. AsEnumerable prevents the second part from being executed as SQL, which would bloat the SQL statement even more. It simply runs in memory.
I consider this a hack because the first part, context.Posts.Take(1) doesn't really have any meaning, it's only there to serve as a wrapper for the three separate queries. It's poor man's query packaging.
It looks like you're doing group joins instead of left outer joins (see this page).
A left outer join looks more like:
var res = from p in context.Posts
join t in context.Trusteeships
on p.Autor.ID equals t.Trustee.ID into tGroup
from tJoin in tGroup.DefaultIfEmpty()
join pat in context.Patients
on p.Autor.ID equals pat.ID into patGroup
from patJoin in patGroup.DefaultIfEmpty()
join s in context.Specialists
on p.Autor.ID equals s.ID into sGroup
from sJoin in sGroup.DefaultIfEmpty()
select ...
Unfortunately, it doesn't seem that Linq can create a query to count elements in each column.
If you don't mind using multiple queries, you could count each separately, for example:
var trusteePost = (from p in context.Posts
join t in context.Trusteeships on p.Autor.ID equals t.Trustee.ID
select t).Count()

Translate from SQL to Linq

I'm really new with this whole Linq story, but I have a good understanding in SQL.
I need to rebuild a query from SQL to Linq. My SQL query is working perfectly and so far I have tried to do something by myself with Linq but without a good result...
Is it possible that someone could help me to translate this query from SQL to Linq?
I'm really ready to learn something new in this whole story. It would be nice if you could why is it working like that in linq.
SQL Statement
SELECT TimeReport.EntryDate
, SUM(TimeReport.Hours) AS Hours
, SUM(BillingRate.HourlyRate * TimeReport.Hours) AS Amount
FROM BillingRate
INNER JOIN Activity
ON BillingRate.BillingRateId = Activity.BillingRateIt
INNER JOIN TimeReport
ON Activity.ActivityId = TimeReport.ActivityId
RIGHT OUTER JOIN Dossier
ON TimeReport.DossierId = Dossier.DossierId
INNER JOIN LBU
ON Dossier.LBUId = LBU.LBUId
INNER JOIN BU
ON Dossier.BUId = BU.BUId
GROUP BY TimeReport.EntryDate
HAVING SUM(TimeReport.Hours) > 0
ORDER BY TimeReport.EntryDate desc
What I have tired with Linq
var x = (from br in ctx.BillingRate
join a in ctx.Activity on br.BillingRateId equals a.BillingRateIt
join tr in ctx.TimeReport on a.ActivityId equals tr.ActivityId
select br)
.GroupJoin(
(from d in ctx.Dossier
join l in ctx.LBU on d.LBUId equals l.LBUId
join b in ctx.BU on d.DossierId equals
Thanks for help and fast answer.
I appreciated every effort !!
Joins aren't necessary when you have navigation properties. Since I don't know exactly what your model looks like, use the following as a starting point and adjust to your own specifications:
// starting with Dossier handles the right outer join condition
var timeRecordQuery =
from d in ctx.Dossier
from tr in d.TimeReports
// this handles the inner join conditions after the right outer join
where d.LBUId.HasValue && d.BUId.HasValue
// project what you want to use
select new
{
EntryDate = tr.EntryDate,
Hours = tr.Hours,
// simply use navigation properties, no need to join
Amount = tr.Activity.BillingRate.HourlyRate * tr.Hours
};
var resultsQuery = from e in timeRecordQuery
// group by EntryDate
group e by e.EntryDate into g
// get sum of hours for each EntryDate value
let hours = g.Sum( x => x.Hours )
// having clause
where hours > 0
// project results
select new
{
EntryDate = g.Key,
Hours = hours,
Amount = g.Sum( x => x.Amount )
};