Nested WHERE IN clause SQL to LINQ - sql

I have a nested WHERE IN clause in my SQL, how would this translate to LINQ, bonus points for using lambda expressions. New to all of this.
SELECT EndowmentID
FROM Criteria c
WHERE c.ID IN(
SELECT CriterionID
FROM Filters
WHERE ChoiceID IN(
SELECT ChoiceID
FROM Responses
WHERE ApplicationID = 1
)
)

This query can definitely be improved using joins...
SELECT EndowmentID
FROM
Criteria C
JOIN Filters F ON C.ID = F.CriterionID
JOIN Responses R ON F.ChoiceID = R.ChoiceID
WHERE R.ApplicationID = 1
Depending on the keys of your tables you might have to SELECT DISTINCT
From there you can write a simple LINQ query:
from c in Criteria
join f in Filters on c.ID equals f.CriterionID
join r in Responses on f.ChoiceID equals r.ChoiceID
where r.ApplicationID = 1
select c.EndowmentID
Again, you might have to Distinct() this.

var result =
Criteria.Where(c =>
Filters.Where(f =>
Responses.Where(r => r.ApplicationId == 1).Select(r => r.ChoiceId)
.Contains(f.ChoiceId)
).Select(f => f.CriterionId)
.Contains(c.Id)
).Select(c => EndowmentId);

Related

How to use subquery in the join function of Yii framework 2 ActiveRecord?

Below is my pure SQL query.
SELECT a.*, b.*
FROM a
INNER JOIN b
ON a.id = b.a_id
INNER JOIN (
SELECT a_id, MAX(add_time) AS max_add_time
FROM b
GROUP BY a_id
) m
ON b.a_id = m.a_id AND b.add_time = m.max_add_time
ORDER BY b.add_time DESC
I have the subquery in the second INNER JOIN. Below my active query.
$subQuery = B::find()->select(['a_id', 'MAX(add_time) AS max_add_time'])->groupBy('a_id');
$query = A::find()->innerJoin('b', 'a.id = b.a_id')
->innerJoin('(' .
$subQuery->prepare(Yii::$app->db->queryBuilder)
->createCommand()
->rawSql
. ') m', 'b.a_id = m.a_id AND a.add_time = m.max_add_time ')
->orderBy('b.add_time DESC');
It works fine, but I do not like the way I use the subquery in the second INNER JOIN. What I want to approach with this query is to select the left table inner join with right table, group by a_id and order by the add_time (DESC) of the right table. How should I better use the subquery in the second INNER JOIN?
The snippet below is untested but it should be something like that. If you read the docs (at http://www.yiiframework.com/doc-2.0/yii-db-query.html#innerJoin()-detail) you can see an array with a subquery is also valid input, with the key being the alias.
$subQuery = B::find()
->select(['a_id', 'MAX(add_time) AS max_add_time'])
->groupBy('a_id');
$query = A::find()
->innerJoin('b', 'a.id = b.a_id')
->innerJoin(['m' => $subQuery], 'b.a_id = m.a_id AND a.add_time = m.max_add_time')
->orderBy('b.add_time DESC');
Having created the join, if you need to use any of the columns returned by the subQuery, you need to add properties to the Yii2 model class, e.g.
$subQuery = FinancialTransaction::find()
->select( new \yii\db\Expression( 'SUM(amount) as owing') )
->addSelect('booking_id')
->groupBy('booking_id');
$query = $query
->addSelect(['b.*', 'owing'])
->leftJoin(['ft' => $subQuery], 'b.booking_display_id = ft.booking_id');
To access "owing" the model has to have the property (PHPdoc optional):
/**
* #var float
*/
public $owing=0;

SQL to LINQ with outer joins and count

I am trying to write the LINQ statement for the following OUTER JOIN with COUNT but can't seem to work it out..
My LINQ skills aren't what they should be yet so any pointer would be greatly appreciated.
The SQL statement in question is:
SELECT b.Id,
b.Text,
b.Active,
COUNT(u.BusinessArea_Id)
FROM dbo.[User] AS u RIGHT OUTER JOIN dbo.BusinessArea AS b ON b.Id = u.BusinessArea_Id
GROUP BY b.Id, b.Text, Active
ORDER BY b.Id
I think you can use a linq like this:
var res = (from ba in businessAreas
let count = users.Count(u => u.BusinessArea_Id == ba.Id)
orderby ba.Id
select new {ba.Id, ba.Text, ba.Active, Count = count}
).ToList();

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()

Converting nested SQL statement to LINQ

I need help to figure out how the following sql statement can be converted into a linq statement
SELECT distinct tableA.x, tableA.y, tableA.z
FROM tableA inner join
tableB on tableA.id = tableB.id inner join
tableC on tableA.id = tableC.id
WHERE (tableB.columnOne IN (SELECT tableX.columnOne
FROM tableX INNER JOIN
tableY ON tableX.xId = tableY.xId
WHERE (tableY.xId = tableC.xId) )
AND (tableB.columnTwo IN (SELECT tableXx.columnTwo
FROM tableXx INNER JOIN
tableYy ON tableXx.XxId = tableYy.XxId
WHERE tableYy.XxId =tableC.XxId)))
)
I suppose the first thing to point out is that you can do sub queries in LINQ. Within your main Where clause, you could probably create queries for your subqueries, (which would result in IQueryable types) and then use .Any() to predict if there are any matches e.g.
var tableXquery = {my tableX subquery};
var tableXxquery = {my tableXx subquery};
var result = context.tableB.where(b => tableXquery.any(x => x.columnOne == b.columnOne) && xx => tableXxquery.any(xx => xx.columnTwo == b.columnTwo));
I hope you get the idea - I just tend to put queries where they need to be, and let LINQ sort it out!

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

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
};