LINQ SELECT with Max and Where in SUBQUERY - c#

I'm trying to code the following SQL statement into LINQ, but struggling. Here is the SQL statement:
SELECT C.ITM_CD, C.BUY_QTY, C.COST
FROM CST C
WHERE C.eff_dt IN (SELECT MAX (D.eff_dt)
FROM CST D
WHERE D.itm_cd = C.itm_cd
AND D.eff_dt <= '22-APR-2014')
ORDER BY C.itm_cd
And here is my LINQ attempt that brings nothing back, even though EVERY eff_dt has a date less than today's date and keep in mind, in my 'real' program this date will be changing.
var results = from c in Csts
let MaxEffDate = (from d in Csts
where d.EffDt <= DateTime.Parse("04/22/2014").Date
&& d.ItmCd == c.ItmCd
select d.EffDt).Max()
where c.EffDt.Equals(MaxEffDate)
select new
{
c.ItmCd,
c.BuyQty,
c.Content
};
Lambda code would be great! So for each itm_cd row in the CST table, I want all the rows to come back that have the Max effective date for that itm_cd as long as the eff_dt <= a certain date. I hard-coded today's date so that I know every row should be coming back, but I get nothing.
I've seen a lot of sub-selects on this site but they always use the MAX function without a WHERE clause of <= for that MAX column.

I can't really test this at the moment, but something like this should get you close:
var results = Csts.Where(d =>
d.EffDt == Csts.Where(x =>
x.ItmCd == d.ItmCd &&
x.EffDt <= DateTime.Now)
.Max(x => x.EffDt))
.OrderBy(d => d.ItmCd)
.Select(d => new { d.ItmCd, d.BuyQty, d.Content });

Related

Linq Select items where they are before a certain date or the first after

I have a Linq-to-Entity query which grabs all the items before a certain date, I was wondering if anyone body knows a way to also grab the first record larger than that date in the same query.
context.Items.Where(x => x.CheckDate < DateTime.Now)
Is there a way to modify this so that I can grab the first date after without making two queries?
var items = from u in context.Items
let dt = (from i in context.Items
where i.CheckDate> DateTime.Now orderby i.CheckDate ).FirstOrDefault()
where u.CheckDate <=dt
select u;
You need two queries, but to merge the results you can use concat,
var result =context.Items.Where(x => x.CheckDate < DateTime.Now).Concat( context.Items.OrderByDescending(t=>t.CheckDate >Yourdatetime).FirstOrDefault());
You don't need to do all of that...
context.Items.Where(x => x.CheckDate < DateTime.Now).OrderByDescending(x => x.CheckDate).FirstOrDefault();

Get range of dates for movie rental

I have a movie rental application. The company would enter the movie rent date and movie rent end date. Dates can overlap (as you can have many customers). The data in the db is stored as
RecordID FromRentDate ToRentDate
1 2016-10-06 18:00:00.000 2016-10-06 20:00:00.000
2 2015-10-06 18:00:00.000 2015-10-06 20:00:00.000
3 2015-09-29 16:00:00.000 2015-09-30 17:00:00.000
4 2015-09-11 00:00:00.000 2015-09-11 00:00:00.000
5 2015-09-09 10:00:00.000 2015-09-09 14:30:00.000
When the user selects a date (using standard .Net controls) the following code is called
IEnumerable<Event> LiveDates = DataContext.Events.Where(d => d.StartDate.Value >= DateTime.Now);
IEnumerable<DateTime> AllLiveDates = null;
if (LiveDates != null && LiveDates.Count() > 0)
{
DateTime FromRentDate = LiveDates.Where(f => f.StartDate.HasValue).Min(f => f.StartDate).Value;
DateTime ToRentDate = LiveDates.Where(t => t.EndDate.HasValue).Max(f => f.EndDate).Value;
AllLiveDates = Enumerable.Range(0, int.MaxValue)
.Select(x => FromRentDate.Date.AddDays(x))
.TakeWhile(x => x <= ToRentDate.Date)
.Where(x => DataContext.Events.Any(c => x >= c.StartDate && x <= c.EndDate));
}
return AllLiveDates.ToList();
What i would like to happen is when a user selects a date, it gets all the dates from the selected date, to the end date including any inclusive dates where the movie is also out so using the above data, if i select todays date I should get all records back and the dates should be listed as:
2015-09-09
2015-09-11
2015-09-29
2015-09-30
2015-10-06 .... etc
Notice how 2015-09-29, 2015-09-30 are included but 2015-09-30 is not a start date. This is because the length of this movie rental is for 2 days (29 and 30 September).
The problem i am experiencing with the above code is that it only returns 1 date. Debugging it it seems to go into AllLiveDates code and something is removing the other dates but not sure what?
You could try something like this
var dateList = new List<DateTime>();
foreach (var ld in LiveDates)
{
for (var dt = ld.StartDate.Date; dt <= ld.EndDate.Date; dt = dt.AddDays(1))
{
dateList.Add(dt);
}
}
dateList = dateList.Distinct().ToList();
dateList = dateList.Sort((a, b) => a.CompareTo(b));
The issue appears to be that you are comparing a date value with a date and time.
Take for example the date 2015-09-09. When you compare that to the DateTime values in your table you should get zero matches, because the DateTime value 2015-09-09 00:00:00.0000 does not lie between the start and end DateTime values of any of your data points.
You will need to strip the time portions of your data points to get the comparison to work the way you want. Fortunately LINQ to SQL supports the .Date property of DateTime values, so this should work:
Try this:
AllLiveDates = Enumerable.Range(0, int.MaxValue)
.Select(x => FromRentDate.Date.AddDays(x))
.TakeWhile(x => x <= ToRentDate.Date)
.Where(x => DataContext.Events.Any(c => x >= c.StartDate.Value.Date && x <= c.EndDate.Value.Date));
Just don't look at the generated SQL... it's not pretty.

Aggregate of DateTime split in Date and Time columns

I have the following query
from booking in query
join ba in Context.BookingAddresses on booking.Id equals ba.BookingId into collections
let firstCollection = (from d in collections where d.AddressType == BookingAddressType.Collection select d.RequestedDate).Min()
where
EntityFunctions.TruncateTime(queryArgs.DateFrom.Value) <= EntityFunctions.TruncateTime(firstCollection) &&
EntityFunctions.TruncateTime(queryArgs.DateTo.Value) >= EntityFunctions.TruncateTime(firstCollection)
select booking;
In the let clause I would actually need the Min of a merged DateOnly and TimeSpan value
d.RequestedDate [DateOnly] + d.RequestedDateTimeFrom [TimeSpan] that look like this in the DB:
RequestedDate : 2013-06-01
RequestedDateTimeFrom : 13:50
This does not compile:
let firstCollection = (from d in collections where d.AddressType == BookingAddressType.Collection select d.RequestedDate + d.RequestedDateTimeFrom)
Edit: In the meantime I thought of a different approach, that would actually solve my main issue, namely, that if there are more datetimes that have both the date and time values the same, I compare them by a thrid Sequence column. So it boils down to simple sorting as this:
from booking in query
join ba in Context.BookingAddresses on booking.Id equals ba.BookingId into collections
let firstCollection = collections.OrderBy(c => c.RequestedDate).ThenBy(c => c.RequestedFromTime).ThenBy(c => c.Sequence).FirstOrDefault()
//(from d in collections where d.AddressType == BookingAddressType.Collection select d.RequestedDate).Min()
where
EntityFunctions.TruncateTime(queryArgs.DateFrom.Value) <= EntityFunctions.TruncateTime(firstCollection.RequestedDate) &&
EntityFunctions.TruncateTime(queryArgs.DateTo.Value) >= EntityFunctions.TruncateTime(firstCollection.RequestedDate)
select booking;
This is maybe not the most elegant way (and untested, to be honest), but I would try an imbricated SqlFunctions.DateAdd with SqlFunctions.DatePart on the TimeSpan
SqlFunctions.DateAdd("hh",
SqlFunctions.DatePart("hh", d.RequestedDateTimeFrom),
SqlFunctions.DateAdd("mi",
SqlFunctions.DatePart("mi", d.RequestedDateTimeFrom),
d.RequestedDate);
(You may also use EntityFunctions.AddHours and EntityFunctions.AddMinutes instead of SqlFunctions.DateAdd)

Get the last entry in database of two different dates and calculate the difference

I have a table that records information from an inverter roughly every 15 minutes. One of the pieces of data that the inverter sends is the kwhtotal which is a number that represents how much total power has been generated through that inverter. I am trying to get the query right for getting the power generated for a particular day. To do that I need to retrieve the last reading on the prior day and compare it to the last reading on the current day.
Here is what I have so far:
DateTime prevStartD = new DateTime((utcStartingDate.AddDays(i - 1)).Year, (utcStartingDate.AddDays(i - 1)).Month, (utcStartingDate.AddDays(i - 1)).Day, 0, 0, 0);
DateTime prevEndD = new DateTime((utcStartingDate.AddDays(i - 1)).Year, (utcStartingDate.AddDays(i - 1)).Month, (utcStartingDate.AddDays(i - 1)).Day, 23, 59, 59);
var previousDay = (from s in db.PowerInverterHistorys
join u in db.PowerInverters on s.inverter_id equals u.id
where u.name == record && (s.recordTime >= prevStartD && s.recordTime <= prevEndD)
orderby s.recordTime descending
select new
{
s.recordTime,
s.kwhtotal
}).Take(1);
DateTime currStartD = new DateTime((utcStartingDate.AddDays(i)).Year, (utcStartingDate.AddDays(i)).Month, (utcStartingDate.AddDays(i)).Day, 0, 0, 0);
DateTime currEndD = new DateTime((utcStartingDate.AddDays(i)).Year, (utcStartingDate.AddDays(i)).Month, (utcStartingDate.AddDays(i)).Day, 23, 59, 59);
var currentDay = (from s in db.PowerInverterHistorys
join u in db.PowerInverters on s.inverter_id equals u.id
where u.name == record && (s.recordTime >= currStartD && s.recordTime <= currEndD)
orderby s.recordTime descending
select new
{
s.recordTime,
s.kwhtotal
}).Take(1);
double? pDay = 0, cDay = 0;
foreach (var p in previousDay) { pDay = p.kwhtotal; }
foreach (var c in currentDay) { cDay = c.kwhtotal; }
var generatedPower = cDay - pDay;
It works and runs, however it is inefficient. It take far to long for the page to open when ran. Is there anything I can do to speed up this query? All I need to do is subtract the kwhtotal of the last entry in the previous day from the kwhtotal of the last entry in the current day.
Could you combine the two queries into one, with the restriction s.recordTime >= prevStartD && s.recordTime <= currEndD and without the Take(1). This will mean only one trip to the database instead of two. Once you have the data set back you can query those results locally using the s.recordTime <= prevEndD condition to find your pDay value, then query again with the s.recordTime >= currStartD condition and look to the last record to get the cDay value.
var previousDay = (from s in db.PowerInverterHistorys
join u in db.PowerInverters on s.inverter_id equals u.id
where u.name == record && (s.recordTime >= prevStartD && s.recordTime <= prevEndD)
orderby s.recordTime descending
select new
{
s.recordTime,
s.kwhtotal
}).Take(1);
OrderBy is expensive operation for the DB if u have large dataset. Use Max() or Min() instead that will give u corresponding first row all the time.
THis should also apply to your "CurrentDay" query.

C# - Linq-To-SQL - Issue with queries

I am thoroughly frustrated right now. I am having an issue with LINQ-To-SQL. About 80% of the time, it works great and I love it. The other 20% of the time, the query that L2S creates returns the correct data, but when actually running it from code, it doesn't return anything. I am about to pull my hair out. I am hoping somebody can see a problem or has heard of this before. Google searching isn't returning much of anything.
Here is the linq query...
var query = from e in DataLayerGlobals.GetInstance().db.MILLERTIMECARDs
where e.deleted_by == -1
&& e.LNAME == lastName
&& e.FNAME == firstName
&& e.TIMECARDDATE == startDate.ToString("MM/dd/yyyy")
group e by e.LNAME into g
select new EmployeeHours
{
ContractHours = g.Sum(e => e.HRSCONTRACT),
MillerHours = g.Sum(e => e.HRSSHOWRAIN + e.HRSOTHER),
TravelHours = g.Sum(e => e.HRSTRAVEL)
};
This is the generated query....
SELECT SUM([t0].[HRSCONTRACT]) AS [ContractHours],
SUM([t0].[HRSSHOWRAIN] + [t0].[HRSOTHER]) AS [MillerHours],
SUM([t0].[HRSTRAVEL]) AS [TravelHours]
FROM [dbo].[MILLERTIMECARD] AS [t0]
WHERE ([t0].[deleted_by] = #p0)
AND ([t0].[LNAME] = #p1)
AND ([t0].[FNAME] = #p2)
AND ([t0].[TIMECARDDATE] = #p3)
GROUP BY [t0].[LNAME]
Now when I plug in the EXACT same values that the linq query is using into the generated query, I get the correct data. When I let the code run, I get nothing.
Any ideas?
What type is TIMECARDDATE? Date, datetime, datetime2, smalldatetime, datetimeoffset or character?
Any chance local date/time settings are messing up the date comparison of startDate.ToString(...)? Since you're sending #p3 as a string, 01/02/2009 may mean Feb 1st or January 2nd, depending on the date/time setting on the server.
My instinct is telling me that you need to be pulling out DataLayerGlobals.GetInstance().db.MILLERTIMECARDs into an IQueryable variable and executing your Linq query against that, although there really should be no difference at all (other than maybe better readability).
You can check the results of the IQueryable variable first, before running the Linq query against it.
To extend this concept a bit further, you can create a series of IQueryable variables that each store the results of a Linq query using each individual condition in the original query. In this way, you should be able to isolate the condition that is failing.
I'd also have a look at the LNAME & FNAME data types. If they're NCHAR/NVARCHAR you may need to Trim the records, e.g.
var query = from e in DataLayerGlobals.GetInstance().db.MILLERTIMECARDs
where e.deleted_by == -1
&& e.LNAME.Trim() == lastName
&& e.FNAME.Trim() == firstName
&& e.TIMECARDDATE == startDate.ToString("MM/dd/yyyy")
group e by e.LNAME into g
select new EmployeeHours
{
ContractHours = g.Sum(e => e.HRSCONTRACT),
MillerHours = g.Sum(e => e.HRSSHOWRAIN + e.HRSOTHER),
TravelHours = g.Sum(e => e.HRSTRAVEL)
};

Categories

Resources