How do I rewrite this SQL query in LINQ format? - c#

I have a sql query that returns the surrounding rows for a given ID. So lets say I'm looking to find 3 rows for a given MediaID of 8000. (Previous Row, Current Row, Next Row).
I'm not even sure if this is the best way to achieve those results but here's my query anyway:
SELECT * FROM (
SELECT TOP 1 * FROM Media WHERE MediaTypeID = 1 and MediaID < 8000 order by MediaID DESC
UNION
SELECT * FROM Media WHERE MediaID = 8000
UNION
SELECT TOP 1 * FROM Media WHERE MediaTypeID = 1 and MediaID > 8000
) AS TBL
ORDER BY TBL.MediaID
I'm importing this query into a C# web application and would like to convert the query over to LINQ format. I'm struggling a bit with this. I think im close.
Here's my LINQ code:
//Get Prev record, current record, next record
var Results = (from m in DB.Media where m.MediaTypeID == 1 && m.MediaID < 8000 orderby m.MediaID descending select m).Take(1).Union(
from m in DB.Media where m.MediaID == 8000 select m).Union(
from m in DB.Media where m.MediaTypeID == 1 && m.MediaID > 8000 select m).Take(1);
Thanks for your help.

It does look really close. I think you need an additional set of parentheses around the final statement your "union"ing (so that the Take(1) only applies to that last statement and not the entire unioned LINQ statement up to that point) and a final OrderBy:
var Results = (from m in DB.Media
where m.MediaTypeID == 1
&& m.MediaID < 8000
orderby m.MediaID descending select m).Take(1)
.Union(
from m in DB.Media
where m.MediaID == 8000
select m)
.Union(
(from m in DB.Media
where m.MediaTypeID == 1
&& m.MediaID > 8000
select m).Take(1))
.OrderBy(m => m.MediaID);
Consider breaking this up into separate lines for clarity:
var lessThan8000 = (from m in DB.Media
where m.MediaTypeID == 1
&& m.MediaID < 8000
orderby m.MediaID descending select m).Take(1);
var equalTo8000 = (from m in DB.Media
where m.MediaID == 8000
select m);
var greaterThan8000 = (from m in DB.Media
where m.MediaTypeID == 1
&& m.MediaID > 8000
select m).Take(1));
var Results = lessThan8000.Union(equalTo8000)
.Union(greaterThan8000)
.OrderBy(m => m.MediaId);

Related

LINQ - select statement in the selected column

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.

SQL to linq query that uses local List<DateTime> as one of the "tables"

I want to do the sql query below using ORM..
We are calculating tickets open at 7 am in morning.
SQL query I have written
CREATE TABLE #Dates (ClosedDate datetime)
INSERT #Dates (ClosedDate) VALUES ('2014-12-21 07:00:00')
INSERT #Dates (ClosedDate) VALUES ('2014-12-22 07:00:00')
INSERT #Dates (ClosedDate) VALUES ('2014-12-23 07:00:00')
select #Dates.ClosedDate, count (t.ID) from tickets t
left join Status st on st.ID = t.StatusID
INNER JOIN #Dates ON ((st.Closed = 0) Or (st.Closed = 1 AND t.ClosedDate > #Dates.ClosedDate )) AND (t.CreatedDate < #Dates.ClosedDate )
DROP TABLE #Dates
LINQ query i have written:
List<DateTime> dates = new List<DateTime>();
for (int i = 0; i < totaldays; i++)
{
dates.Add(fromDate.AddDays(i));
}
var datesQuery = dates.AsQueryable();
var dateAndCounts = (
from t in tickets
join s in statuses
on ev.StatusID equals s.ID
join dat in dates
on (
(!s.Closed.Value || (s.Closed.Value && (ev.Closed >= dat)))
&& (t.Created < dat)
)
select new { dat, count(t.Event_ID) });
Join on date not working and I do not know how to do count on id. SQL is working fine.
Generally ORMs like linq2sql and EntityFramework don't play too nicely with mix-and-matched tables in memory and DB. (See this question for more info.)
The simplest way to get around this limitation is by just querying the database once for each date you're looking at. Since it's three, it's probably not a big deal to take a DB hit each time (unless this is in the middle of some highly performant system, in which case it can probably be solved with a little caching).
Just moving the query into the for loop and whereing on that individual DateTime, then aggregating the results manually should do the trick:
var results = new Dictionary<DateTime, int>();
for (int i = 0; i < totaldays; i++)
{
var date = fromDate.AddDays(i);
var tickets = (from t in tickets
join s in statuses on t.StatusID equals s.ID
where (!s.Closed.Value || (s.Closed.Value && t.Closed >= date)))
&& t.Created < date
).Count();
results[date] = tickets;
}
This then leaves you with a mapping of DateTimes to number of Tickets that match your filter for that particular time.
You're going to have to do this in two runs:
One get the data from the database,
Join the data from the database to the in-memory table.
You can pass the in-memory dates to the query, but if they are a series of sequential dates, it might be easier to just pass in the start and end-date.
I'd opt to grab all matching tickets in one go, not in a for loop, as that would potentially throw a lot of queries at the database:
var minDate = dates.Min();
var matchingTickets = (
from t in tickets
join s in statuses
on t.StatusId equals s.StatusId
where
(!s.Closed.HasValue || (s.Closed.HasValue && (s.Closed >= minDate)))
&& (t.Created < minDate)
select new { s.Closed, t.Created, t.EventId }).ToList();
Now that we have the tickets that match in memory, we can do the second pass and join them to the in-memory dates table. Since you want to join on an operation that is different than a simple equals we need to use the from syntax instead of a join syntax:
var datesAndCounts = from t in matchingTickets
from d in dates
where
(!t.Closed.HasValue || (t.Closed.HasValue && t.Closed >= d))
&& t.Created < d
group t by d into newGroup
orderby newGroup.Key
select newGroup;
This can be enumerated like:
foreach (var item in datesAndCounts)
{
Console.WriteLine(item.Key);
Console.WriteLine(item.Count());
}
A quick test application shows the results here on pastebin.

NHibernate join on aggregated count of self

Hi I am new to NHibernate and having trouble doing a join on an aggregated set of self. I think showing the SQL I am trying to achieve might help:
The Data Looks Like this:
Message TABLE
ID DIRECTION BATCHID SEQUENCE ISLASTINSEQUENCE
1 Outbound 1 1 0
2 Outbound 1 2 0
3 Outbound 1 3 1
The query looks like this:
--Get all msgs with batchId where full sequence is ready
SELECT *
FROM [Message] M
JOIN (
--Group Msg on batchId and Count Sequence + cross ref using having
SELECT M.BatchId
FROM [Message] M
JOIN (
--Get BatchId of last msg in sequence
SELECT BatchId, Sequence as LastInSequence
FROM [Message]
WHERE Direction = 'Outbound'
AND IsEndOfSequence = 1
) M1 ON M.BatchId = M1.BatchId
WHERE Direction = 'Outbound'
GROUP BY M.BatchId, LastInSequence
HAVING count(1) = M1.LastInSequence
) B ON M.BatchId = B.BatchId
Basically I want to include batches where I have the full sequence
Here is may HNibernate Linq attempt:
var lastInSeqenceMsgs =
from b in Query<Message>(x => x.Direction == MessageDirection.Outbound
&& x.IsEndOfSequence)
select new {b.BatchId, LastInSequence = b.Sequence};
var fullSequenceBatchIds =
from outboundMessage in Query<Message>(x => x.Direction ==
MessageDirection.Outbound)
join lastInSequence in (lastInSeqenceMsgs)
on outboundMessage.BatchId equals lastInSequence.BatchId
group lastInSequence by new {lastInSequence.BatchId, lastInSequence.LastInSequence}
into g
where g.Count() == g.Key.LastInSequence
select g.Key.BatchId;
var allMsgsFromWithCompleteSequences =
from fullSequenceMessage in Query<Message>(x => x.Direction ==
MessageDirection.Outbound)
join test in (fullSequenceBatchIds) on
fullSequenceMessage.BatchId equals test.BatchId
select test;
To which it bombs on the second query (I evaluated the queries - not shown) with the following exception:
Unable to cast object of type 'System.Linq.Expressions.NewExpression' to type 'Remotion.Linq.Clauses.Expressions.QuerySourceReferenceExpression'.
Of which I refined back to the join on self
var fullSequenceBatchIds =
from outboundMessage in Query<Message>(x => x.Direction ==
MessageDirection.Outbound)
join lastInSequence in (lastInSeqenceMsgs)
on outboundMessage.BatchId equals lastInSequence.BatchId
select outboundMessage;
To which I get the exception
"Specified method is not supported."
I am getting brick imprints on my forehead, so some help here would be greatly appreciated.
In general I can just say that the nhibernate Linq provider does NOT support everything what Linq offers.
In addition, nhibernate does NOT support sub queries. It simply doesn't work and you have to work around it somehow.

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

linq-to-sql Concat() throwing a System.IndexOutofRangeException

I'm struggling with an exception using linq-to-sql Concat()
I've got 2 tables.
The first table, ParsedMessages, has the following fields
* ParsedMessageID (int)
* MessageTypeID (int)
* TextMessage (varchar(max))
The second table, ParsedMessageLinks, has the following fields
* ParsedMessageID (int)
* AnotherID (int)
* NumberOfOccurences (int)
This is what I need to achieve using a single linq query but I'm not sure if it's possible or not.
Through a join, retrieves ParsedMessage records that links to a certain AnotherID. In example SQL and linq code, the AnotherID will have the value 0 just for the purpose of having an example.
For each ParsedMessage record, I also need the NumberOfOccurences (field of table #2)
Retrieve only the top(100) ParsedMessage records for each MessageTypeID. So for example, if there is 275 records in ParsedMessages that links to AnotherID==0 where the first 150 records have MessageTypeID == 0 and the remaining 125 records having MessageTypeID == 1, I want my query to end up returning 200 records, the top(100) with MessageTypeID == 0 and the top(100) with MessageTypeID == 1
After a lot of search, I've found that the plain SQL equivalent of I what I want to do is this. I knew that this exists first end, but I tried to find something else without Union all at first and fail to do so (my SQL knowledge is not that good) :
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 0 ORDER BY PM.ParsedMessageID DESC UNION ALL
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 1 ORDER BY PM.ParsedMessageID DESC UNION ALL
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 2 ORDER BY PM.ParsedMessageID DESC
So basically, the only way to retrieve the data I need is to do 3 sql queries in a single pass where only the PM.MessageTypeID is different for each query.
Now I wanted to achieve this using linq-to-sql. After googling, I've found that I could use the Linq Concat() method to reproduce a SQL Union All.
Here are some links pointing to what I thought would work :
http://blog.benhall.me.uk/2007/08/linq-to-sql-difference-between-concat.html
EF. How to union tables, sort rows, and get top entities?
I end up having this exception :
System.IndexOutOfRangeException : "Index was outside the bounds of the array."
Here's the faulty code :
IQueryable<MyObject> concatquery;
int[] allMessageTypeIDs = new int[] { 0, 1, 2 };
for (int mt = 0; mt < allMessageTypeIDs.Length; mt++)
{
if (mt == 0)
{
concatquery = (from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == allMessageTypeIDs[mt]
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
else
{
concatquery = concatquery.Concat(from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == allMessageTypeIDs[mt]
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
}
var results = concatquery.ToArray();
I've declared the int array allMessageTypeIDs, for simplicity. But remember that the values it holds may differ, so that's why I've added the for loop. Maybe it's "illegal" to use a Concat() in a loop that way, but I could not find any relevant information on this exception.
The class MyObject basically hold a int (NumberOfOccurences) and a ParsedMessage database object, nothing else.
Any suggestions on what could be wrong with my code that causes the exception?
Thanks
Francis
Never use the variable you're looping with in your Linq queries. It just doesn't work. You want to assign a new temporary variable to use instead.
IQueryable<MyObject> concatquery;
int[] allMessageTypeIDs = new int[] { 0, 1, 2 };
for (int mt = 0; mt < allMessageTypeIDs.Length; mt++)
{
var myItem = allMessageTypeIDs[mt]; // <-- HERE!
if (mt == 0)
{
concatquery = (from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == myItem
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
else
{
concatquery = concatquery.Concat(from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == myItem
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
}
var results = concatquery.ToArray();

Categories

Resources