I wish to join two tables while filtering one of the tables. That works fine like
var matching = from a in ctx.A
join b in ctx.B on a.BId equals b.Id
where idList.Contains(b.Id)
select a;
However, if I also make use of the into keyword to name the joined result
var matching = from a in ctx.A
join b in ctx.B on a.BId equals b.Id into c
where idList.Contains(b.Id)
select a;
I get a compiler error telling me
The name 'b' does not exist in the current context
However, I can reference a at that point, as well as 'c', without problems.
Why is that exactly, and how can I apply a where clause to b?
Why is that exactly
Because after a join into clause, the range variable introduced by that clause isn't in scope - whereas previous variables are. Don't forget that you're joining into c, so each value of b is effectively part of the group of values (c).
and how can I apply a where clause to b?
By doing it earlier:
var matching = from a in ctx.A
join b in ctx.B.Where(x => idList.Contains(x.Id))
on a.BId equals b.Id into c
where c.Any()
select a;
EDIT: This can be put into slightly more query-expression-oriented code as:
var matchingBs = from b in ctx.B
where idList.Contains(b.Id)
select b;
var matching = from a in ctx.A
join b in matchingBs
on a.BId equals b.Id into c
where c.Any()
select a;
(You could use a nested query expression, but I'm not keen on those in general.)
Or using Any on c:
var matching = from a in ctx.A
join b in ctx.B on a.BId equals b.Id into c
where c.Any(b => idList.Contains(b.Id))
select a;
Or even:
var matching = from a in ctx.A
where ctx.B.Any(b => idList.Contains(x.Id) &&
a.BId == b.Id)
select a;
Which can be rewritten as:
var matching = ctx.A.Where(a => ctx.B.Any(b => idList.Contains(x.Id) &&
a.BId == b.Id));
It's important to understand the difference in results between join and join into - the first creates a "pairwise" join; the second creates a group join, where the result for the extra range variable is a group of matches.
Related
I'm trying to find all entries in table a, where there is no matching entry in table b for one specific column (order). I'm using the following:
SELECT *
FROM a
LEFT OUTER JOIN b
ON a.id = b.id
WHERE b.order IS NULL
AND a.result>10
However, the last condition for result doesn't seems to work. It simply lists all the entries from table a, regardless whether result is more than 10 or not.
Any way around this?
Shouldn't your query be as below?
SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.id WHERE b.id IS NULL AND a.result>10
select A.UNIT, A.LEASE_ID, A.MONTHS_GUARANTEED, A.MONTHLY_PAYMENT_AM,
B.DATE_PAID, C.CHARGE_JOB from leasei A
left outer join eq_capture B on A.LEASE_ID = B.LEASE_ID
left outer join eq_mast C on A.UNIT = C.UNIT
where A.DATE_LEASE_EXPIRE = 0
ORDER BY A.LEASE_ID;
I want to use the result of the above query that is use the value C.CHARGE_JOB and the check with another table (job_infojc D) and get the D.STATE value with a where condition C.CHARGE_JOB = D.JOB
Any help is highly appreciated.
Like this:
select D.STATE, X.*
from job_infojc D
join (select A.UNIT, A.LEASE_ID, A.MONTHS_GUARANTEED, A.MONTHLY_PAYMENT_AM,
B.DATE_PAID, C.CHARGE_JOB from leasei A
left outer join eq_capture B on A.LEASE_ID = B.LEASE_ID
left outer join eq_mast C on A.UNIT = C.UNIT
where A.DATE_LEASE_EXPIRE = 0
) X on X.CHARGE_JOB = D.JOB
ORDER BY X.LEASE_ID;
This statement won't compile:
query = from g in context.GridViews
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
where g.GridTypeID == id && ( g.IsShared == true || g.RepID == me.clsRep.OID)
&& f.RepID == me.clsRep.OID
select g;
The compiler error is this (and it's underlining the last part of the where clause:
The name 'f' does not exist in the current context
It's logical SQL counterpart would be:
declare #RepID int
declare #GridTypeID int
select #RepID=15, #GridTypeID=5
select g.*,f.*
from
GridViews g
left outer join GridViewFavorites f on f.GridViewID = g.ID
where
g.GridTypeID = #GridTypeID and (g.IsShared = 1 or g.RepID == #RepID)
and f.RepID == #RepID
NOTE: per #hdv 's good catch the SQL sample should actually be:
select g.*,f.*
from
GridView g
left outer join GridViewFavorite f on f.GridViewID = g.ID and f.RepID = #RepID
where
g.GridTypeID = #GridTypeID and (g.IsShared = 1 or g.RepID = #RepID)
It's the "into" part of your join - once you've joined "into" a group, the join variable (f in this case) is out of scope - you've got to use gf instead. Alternatively, given that you're not actually using gf in your query at all, maybe you should just get rid of the into gf part entirely so it's a normal join instead of a group join.
However, that won't give you a left outer join. If you want a left outer join, you might want:
query = from g in context.GridViews
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
from f2 in gf.DefaultIfEmpty()
where g.GridTypeID == id && (g.IsShared == true || g.RepID == me.clsRep.OID)
&& (f2 == null || f2.RepID == me.clsRep.OID)
select g;
The left-join pattern for LINQ goes like this:
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
from f in gf.DefaultIfEmpty() //missing
More information is available on Stack Overflow and Google.
The where clause acts on the joined result, so the f variable is not in scope.
I'm new to LINQ and have very little knowledge.
I have the following complex query. it runs 3 or 4 times slower than the stored procedure which i translated to LINQ.
any tips for me to make it run faster?
var result = from a in db.A
join al in db.AL.Where(q => q.CurrentLocation == 1) on a.AID equals al.AID into tmp_al
from al in tmp_al.DefaultIfEmpty()
join l in db.Lon al.LID equals l.LID into tmp_l
from l in tmp_l.DefaultIfEmpty()
join r in db.R on l.RID equals r.RID into tmp_r
from r in tmp_r.DefaultIfEmpty()
join b in db.B on r.BID equals b.BID into tmp_b
from b in tmp_b.DefaultIfEmpty()
join ap in db.AP.Where(q => q.CurrentProtocol == 1) on a.AID equals ap.AID into tmp_ap
from ap in tmp_ap.DefaultIfEmpty()
join p in db.P on ap.PID equals p.PID into tmp_p
from p in tmp_p.DefaultIfEmpty()
join s in db.S on a.SID equals s.SID into tmp_s
from s in tmp_s.DefaultIfEmpty()
join ans in db.AS on a.ASID equals ans.ASID into tmp_ans
from ans in tmp_ans.DefaultIfEmpty()
join pr in db.P on p.PI equals pr.PID into tmp_pr
from pr in tmp_pr.DefaultIfEmpty()
where a.Active == 1
group a by new { a.Active, pr.LN, pr.FN, b.BN, r.RID, r.R1, p.PN, s.S1, ans.AS1 }
into grp
orderby grp.Key.BN, grp.Key.R1, grp.Key.PN, grp.Key.S1, grp.Key.AS1
select new
{
PIName = grp.Key.LN + " " + grp.Key.FN,
BN = grp.Key.BN,
RID = grp.Key.RID,
R = grp.Key.R1,
PN = grp.Key.PN,
S = grp.Key.S1,
AS = grp.Key.AS1,
NumberOA = grp.Count()
};
Thanks for your answers. #Albin Sunnanbo: i dont know how to check the execution plans. my LINQ runs correctly and produces the required output. it is just slow. I would like to speeden it up. #usr: the original sql is as follows:
sorry about the silly table names. the original code is confidential. so i'm not posting the complete table names.
CREATE PROCEDURE [dbo].[report_CBRP] --
AS
SELECT LN + ' ' + FN As PIN, BN, R.RID, R, PN,
S, AS, COUNT(*) As NOA
FROM A
LEFT JOIN AL
ON A.AID = AL.AID
AND AL.CL = 1
LEFT JOIN L
ON AL.LID = L.LID
LEFT JOIN R
ON L.RID = R.RID
LEFT JOIN B
ON R.BID = B.BID
LEFT JOIN AP
ON A.AID = AP.AID
AND AP.CPl = 1
LEFT JOIN P
ON AP.PID = P.PID
LEFT JOIN S
ON A.SID = S.SID
LEFT JOIN AS
ON A.ASID = AS.ASID
LEFT JOIN P
ON P.PI = P.PID
GROUP BY A.A, LN , FN , B.BN, R.RID, R.R, P.PN,
S.S, AS.AS
HAVING A.A = 1
ORDER BY B.BN, R.R, P.PN, S, AS
GO
It seems you're doing SQL hard life here.
In general, try to avoid so many joins, but rather break them into few small queries.
More than that, you're performing a group by which in itself is an expensive operation, let alone with so many columns
I've noticed that you're joining all the columns in each table. Try to select only the relevant columns.
Also noticed that few of the tables aren't used in the group by like al, ap and l. Do you need them at all??
Use AsNoTracking() for readonly data from EF. In that way you speed up things.
Use SQL Views
I have this simple sql query:
select c.LastName, Sum(b.Debit)- Sum(b.Credit) as OpenBalance from Balance as b
inner join Job as j on (b.Job = j.ID)
inner join Client as c on (j.Client = c.ID)
Group By c.LastName
and I am trying to convert it to work in linq like this:
from b in Balance
join j in Job on b.Job equals j.ID
join c in Client on j.Client equals c.ID
group b by new { c.LastName } into g
select new {
Name = c.Lastname,
OpenBalance = g.Sum(t1 => t1.Credit)
}
but when I try to run it in LINQPad I get the following message:
The name 'c' does not exist in the
current context
and it highlights c.Lastname in select new statement.
Any help on this will be greatly appreciated.
Thank you.
Well, you've grouped b by c.LastName. So after the grouping operation, you're dealing with g which is a grouping with the element type being the type of b, and the key type being the type of c.LastName. It could well be that all you need is:
select new {
Name = g.Key,
OpenBalance = g.Sum(t1 => t1.Credit)
}
... but if you need to get at any other aspects of c, you'll need to change your grouping expression.