LINQ - select statement in the selected column - c#

i am intend to convert the following query into linQ
SELECT TOP 100 S.TxID,
ToEmail,
[Subject],
ProcessedDate,
[Status] = (CASE WHEN EXISTS (SELECT TxID FROM TxBounceTracking
WHERE TxID = S.TxID)
THEN 'Bounced'
WHEN EXISTS (SELECT TxID FROM TxOpenTracking
WHERE TxID = S.TxID)
THEN 'Opened'
ELSE 'Sent' END)
FROM TxSubmissions S
WHERE S.UserID = #UserID
AND ProcessedDate BETWEEN #StartDate AND #EndDate
ORDER BY ProcessedDate DESC
The following code is the linq that i converted.
v = (from a in dc.TxSubmissions
where a.ProcessedDate >= datefrom && a.ProcessedDate <= dateto && a.UserID == userId
let bounce = (from up in dc.TxBounceTrackings where up.TxID == a.TxID select up)
let track = (from up in dc.TxOpenTrackings where up.TxID == a.TxID select up)
select new { a.TxID, a.ToEmail, a.Subject,
Status = bounce.Count() > 0 ? "Bounced" : track.Count() > 0 ? "Opened" : "Sent",
a.ProcessedDate });
However this linq is too slow because the bounce and track table, how should i change the linq query to select one row only to match the SQL query above >>
SELECT TxID FROM TxOpenTracking WHERE TxID = S.TxID
in my selected column, so it can execute faster.
Note that the record contained one million records, thats why it lag

As you don't care about readability because you will end up generating the query via EF you can try to join with those two tables. (it looks that TxID is a FK or a PK/FK)
More about JOIN vs SUB-QUERY here: Join vs. sub-query
Basically your SQL looks like this:
SELECT TOP 100 S.TxID, ToEmail, [Subject], ProcessedDate,
[Status] = (CASE WHEN BT.TxID IS NOT NULL
THEN 'Bounced'
WHEN OP.TxID IS NOT NULL
THEN 'Opened'
ELSE 'Sent' END)
FROM TxSubmissions S
LEFT JOIN TxBounceTracking BT ON S.TxID = BT.TxID
LEFT JOIN TxOpenTracking OP ON S.TxID = OP.TxID
WHERE S.UserID = #UserID
AND ProcessedDate BETWEEN #StartDate AND #EndDate
ORDER BY ProcessedDate DESC
And then, you can try to convert it to LINQ something like:
v = (from subs in dc.TxSubmissions.Where(sub => sub.ProcessedDate >= datefrom && sub.ProcessedDate <= dateto && sub.UserID == userId)
from bts in dc.TxBounceTrackings.Where(bt => bt.TxID == subs.TxID).DefaultIfEmpty()
from ots in dc.TxOpenTrackings.Where(ot => ot.TxID == subs.TxID).DefaultIfEmpty()
select new { });
More about left join in linq here: LEFT JOIN in LINQ to entities?
Also if you remove default if empty you'll get a inner join.
Also you need to take a look at generated SQL in both cases.

Related

ORA-00907: Distinct, join and group by in LINQ C#

I'm getting the error code ORA-00907, when executing the linq query below. It seems to be Oracle specific. The problem seems to be the "group by" subquery.
Lets say I have these two tables: USER and ADDRESS, with columns:
USER{userid, addressid},
ADDRESS{addressid, streetname}
Table ADDRESS contains several rows with the same addressid, so I guess I would like to group the ADDRESS-table (DISTINCT) on the addressid so I only get one match with addressid in USER-table, it should also be a LEFT JOIN, so if there is no match I still get the USER-record.
I have tried several different approaches, My code (example):
List<MyObject> result =
(
from u in context.USER.Where(i => i.userid > 100)
join a in (from address in context.ADDRESS group address by address.addressid)
on u.addressid equals a.FirstOrDefault().addressid into joinedaddress
from lfjoinedaddress in joinedaddress.DefaultIfEmpty()
join email in context.EMAIL on u.userid equals email.userid into jemail
from lfjemail in jemail.DefaultIfEmpty()
select new MyObject()
{
UserId = u.userid,
StreetName = lfjoinedaddress.streetname,
UserEmail = lfjemail.emailaddress
}
).ToList();
Someone know how to achieve this, by rewriting the query so it works against Oracle.
UPDATE:
This is the generated sql-query, except the "email":
SELECT
1 AS "C1",
"Extent1"."USERID" AS "USERID",
"Extent1"."ADDRESSID" AS "ADDRESSID"
FROM (SELECT
"USER"."USERID" AS "USERID",
"USER"."ADDRESSID" AS "ADDRESSIF",
FROM "EXT"."USER" "USER") "Extent1"
LEFT OUTER JOIN (SELECT "Distinct1"."ADDRESSID" AS "ADDRESSID1", "Limit1"."ADDRESSID" AS "ADDRESSID2", , "Limit1"."STREETNAME" AS "STREETNAME1"
FROM (SELECT DISTINCT
"Extent2"."ADDRESSID" AS "ADDRESSID"
FROM (SELECT
"ADDRESS"."ADDRESSID" AS "ADDRESSID",
"ADDRESS"."STREETNAME" AS "STREETNAME",
FROM "EXT"."ADDRESS" "ADDRESS") "Extent2" ) "Distinct1"
OUTER APPLY (SELECT "Extent3"."ADDRESSID" AS "ADDRESSID", "Extent3"."STREETNAME" AS "STREETNAME"
FROM (SELECT
"ADDRESS"."ADDRESSID" AS "ADDRESSID",
"ADDRESS"."STREETNAME" AS "STREETNAME",
FROM "EXT"."ADDRESS" "ADDRESS") "Extent3"
WHERE ("Distinct1"."ADDRESSID" = "Extent3"."ADDRESSID") AND (ROWNUM <= (1) ) ) "Limit1"
OUTER APPLY (SELECT "Extent4"."ADDRESSID" AS "ADDRESSID", , "Extent4"."STREETNAME" AS "STREETNAME"
FROM (SELECT
"ADDRESS"."ADDRESSID" AS "ADDRESSID",
"ADDRESS"."STREETNAME" AS "STREETNAME",
FROM "EXT"."ADDRESS" "ADDRESS") "Extent4"
WHERE ("Distinct1"."ADDRESSID" = "Extent4"."ADDRESSID") AND (ROWNUM <= (1) ) ) "Limit2" ) "Apply2" ON ("Extent1"."ADDRESSID" = "Apply2"."ADDRESSID2") OR (("Extent1"."ADDRESSID" IS NULL) AND ("Apply2"."ADDRESSID3" IS NULL))))
DISTINCT is applied to tuples not an individual value within a tuple. If STREETNAME is always the same per ADDRESSID in table ADDRESS, then you want DISTINCT tuples of (ADDRESSID, STREETNAME). Which you could simply do with selecting the distinct columns of context.ADDRESS as your subquery and omit the .FirstOrDefault().
join a in
(
from address in context.ADDRESS
select new
{
address.addressid,
address.streetname
}
).Distinct()
on u.addressid equals a.addressid into joinedaddress
from lfjoinedaddress in joinedaddress.DefaultIfEmpty()
If STREETNAME is not always the same per ADDRESSID, then you don't want DISTINCT at all.

Complicated SQL Server query

I am trying to write an SQL (Server) query which will return all events on a current day, and for all events where the column recurring= 1, I want it to return this event on the day it is being held and for the subsequent 52 weeks following the event.
My tables are structured as followed :
Event
{
event_id (PK)
title,
description,
event_start DATETIME,
event_end DATETIME,
group_id,
recurring
}
Users
{
UserID (PK)
Username
}
Groups
{
GroupID (PK)
GroupName
}
Membership
{
UserID (FK)
GroupID (FK)
}
The code I have thus far is as follows :
var db = Database.Open("mPlan");
string username = HttpContext.Current.Request.Cookies.Get("mpUsername").Value;
var listOfGroups = db.Query("SELECT GroupID FROM Membership WHERE UserID = (SELECT UserID from Users WHERE Username = #0 )", username);
foreach(var groupID in listOfGroups)
{
int newGroupID = groupID.GroupID;
var result = db.Query(
#"SELECT e.event_id, e.title, e.description, e.event_start, e.event_end, e.group_id, e.recurring
FROM event e
JOIN Membership m ON m.GroupID = e.group_id
WHERE e.recurring = 0
AND m.GroupID = #0
AND e.event_start >= #1
AND e.event_end <= #2
UNION ALL
SELECT e.event_id, e.title, e.description, DATEADD(week, w.weeks, e.event_start), DATEADD(week, w.weeks, e.event_end), e.group_id, e.recurring
FROM event e
JOIN Membership m ON m.GroupID = e.group_id
CROSS JOIN
( SELECT row_number() OVER (ORDER BY Object_ID) AS weeks
FROM SYS.OBJECTS
) AS w
WHERE e.recurring = 1
AND m.GroupID = #3
AND DATEADD(WEEK, w.Weeks, e.event_start) >= #4
AND DATEADD(WEEK, w.Weeks, e.event_end) <= #5", newGroupID, start, end, newGroupID, start, end
);
This results in when one queries for the date of the event stored in the database, this event and 52 weeks of events are returned. When one queries for the event the week after this one, nothing is returned.
The simplest solution would be to alter the following 2 lines
AND e.event_start >= #4
AND e.event_end <= #5"
to
AND DATEADD(WEEK, w.Weeks, e.event_start) >= #4
AND DATEADD(WEEK, w.Weeks, e.event_end) <= #5"
However, I'd advise putting all this SQL into a stored procedure, SQL-Server will cache the execution plans and it will result in (slightly) better performance.
CREATE PROCEDURE dbo.GetEvents #UserName VARCHAR(50), #StartDate DATETIME, #EndDate DATETIME
AS
BEGIN
-- DEFINE A CTE TO GET ALL GROUPS ASSOCIATED WITH THE CURRENT USER
;WITH Groups AS
( SELECT GroupID
FROM Membership m
INNER JOIN Users u
ON m.UserID = u.UserID
WHERE Username = #UserName
GROUP BY GroupID
),
-- DEFINE A CTE TO GET ALL EVENTS FOR THE GROUPS DEFINED ABOVE
AllEvents AS
( SELECT e.*
FROM event e
INNER JOIN Groups m
ON m.GroupID = e.group_id
UNION ALL
SELECT e.event_id, e.title, e.description, DATEADD(WEEK, w.weeks, e.event_start), DATEADD(WEEK, w.weeks, e.event_end), e.group_id, e.recurring
FROM event e
INNER JOIN Groups m
ON m.GroupID = e.group_id
CROSS JOIN
( SELECT ROW_NUMBER() OVER (ORDER BY Object_ID) AS weeks
FROM SYS.OBJECTS
) AS w
WHERE e.recurring = 1
)
-- GET ALL EVENTS WHERE THE EVENTS FALL IN THE PERIOD DEFINED
SELECT *
FROM AllEvents
WHERE Event_Start >= #StartDate
AND Event_End <= #EndDate
END
Then you can call this with
var result = db.Query("EXEC dbo.GetEvents #0, #1, #2", username, start, end);
This elimates the need to iterate over groups in your code behind. If this is actually a requirement then you could modify the stored procedure to take #GroupID as a parameter, and change the select statements/where clauses as necessary.
I have assumed knowledge of Common Table Expressions. They are not required to make the query work, they just make things slightly more legible in my opinion. I can rewrite this without them if required.
I would check my parameters one at a time against some trivial SQL, just to rule them out as possible culprits. Something like this:
var result = db.Query("select r=cast(#0 as varchar(80))",username);
var result = db.Query("select r=cast(#0 as int)",newGroupID);
var result = db.Query("select r=cast(#0 as datetime)",start);
var result = db.Query("select r=cast(#0 as datetime)",end);

LINQ to EF, Left Join and group by clause

I have this SQL:
select o.prod_id, SUM(o.[count]) as [count]
into #otgr
from otgr o
where o.[date]<= #date
group by o.prod_id
select f.prod_id, SUM(f.[count]) as [count]
into #factory
from factory f
where f.[date]<= #date
group by f.prod_id
select p.name, p.id, f.[count] - ISNULL(o.[count],0) as av_count
from products p
join #factory f on f.prod_id = p.id
left join #otgr o on o.prod_id = p.id
where f.[count] - ISNULL(o.[count],0) > 0
How can I translate this into Linq? I'm stuck with this code:
from otgrr in db.otgr
where otgrr.date <= date
group otgrr by otgrr.prod_id into otgrs
from fac in db.factory
where fac.date <= date
group fac by fac.prod_id into facs
from prod in db.products
join fac2 in facs on prod.id equals fac2.Key
join otg2 in otgrs.DefaultIfEmpty(new {id = 0, av_count = 0 }) on prod.id equals otg2.Key
where (fac2.SUM(a=>a.av_count) - otg2.SUM(a=>a.av_count)) > 0
select new products { id = prod.id, name = prod.name, av_count = (fac2.SUM(a=>a.av_count) - otg2.SUM(a=>a.av_count))
Thank to everyone, and sorry for my bad english
You can also check LINQPad.
Of course, you can split this into multiple LINQ queries (after all, the execution is deferred, so it will be executed all as one single query, without using temporary tables. It should be faster in 99% of the cases).
But in your case it can be written more simply, by using navigation properties you probably have already set up:
var result= from p in products
select new {Name=p.Name,
Id = p.Id,
Count = p.Factories.Where(f=> f.date <= date).Sum(f=>f.Count)
- p.otgrs.Where(o=> o.date <= date).Sum(o=>o.Count)
};

How can I optimise this Linq query to remove the unnecessary SELECT Count(*)

I have three tables, Entity, Period and Result. There is a 1:1 mapping between Entity and Period and a 1:Many between Period and Result.
This is the linq query:
int id = 100;
DateTime start = DateTime.Now;
from p in db.Periods
where p.Entity.ObjectId == id && p.Start == start
select new { Period = p, Results = p.Results })
This is relevant parts of the generated SQL:
SELECT [t0].[EntityId], [t2].[PeriodId], [t2].[Value], (
SELECT COUNT(*)
FROM [dbo].[Result] AS [t3]
WHERE [t3].[PeriodId] = [t0].[Id]
) AS [value2]
FROM [dbo].[Period] AS [t0]
INNER JOIN [dbo].[Entity] AS [t1] ON [t1].[Id] = [t0].[EntityId]
LEFT OUTER JOIN [dbo].[Result] AS [t2] ON [t2].[PeriodId] = [t0].[Id]
WHERE ([t1].[ObjectId] = 100) AND ([t0].[Start] = '2010-02-01 00:00:00')
Where is the SELECT Count(*) coming from and how can I get rid of it? I don't need a count of the "Results" for each "Period" and it slows the query down by an order of magnitude.
Consider using the Context.LoadOptions and specifying for Period to LoadWith(p => p.Results) to eager load the period with results without needing to project into an anonymous type.

Linq To Sql Left outer join - filtering null results

I'd like to reproduce the following SQL into C# LinqToSql
SELECT TOP(10) Keywords.*
FROM Keywords
LEFT OUTER JOIN IgnoreWords
ON Keywords.WordID = IgnoreWords.ID
WHERE (DomainID = 16673)
AND (IgnoreWords.Name IS NULL)
ORDER BY [Score] DESC
The following C# Linq gives the right answer.
But I can't help think I'm missing something (a better way of doing it?)
var query = (from keyword in context.Keywords
join ignore in context.IgnoreWords
on keyword.WordID equals ignore.ID into ignored
from i in ignored.DefaultIfEmpty()
where i == null
where keyword.DomainID == ID
orderby keyword.Score descending
select keyword).Take(10);
the SQL produced looks something like this:
SELECT TOP (10)
[t0].[DomainID]
, [t0].[WordID]
, [t0].[Score]
, [t0].[Count]
FROM [dbo].[Keywords] AS [t0]
LEFT OUTER JOIN
( SELECT 1 AS [test]
, [t1].[ID]
FROM [dbo].[IgnoreWords] AS [t1]
) AS [t2]
ON [t0].[WordID] = [t2].[ID]
WHERE ([t0].[DomainID] = 16673)
AND ([t2].[test] IS NULL)
ORDER BY [t0].[Score] DESC
How can I get rid of this redundant inner selection?
It's only slightly more expensive but every bit helps!
I think you can do something like this to eliminate the left join and maybe get more efficiency:
var query = (from keyword in context.Keywords
where keyword.DomainID == ID
&& !(from i in context.IgnoreWords select i.ID).Contains(keyword.WordID)
orderby keyword.Score descending
select keyword)
.Take(10);

Categories

Resources