entity framework grouping by datetime - linq

Every 5 minutes a row in a sql server table is added. The fields are:
DateTime timeMark,Int total.
Using entity framework I want to populate a new list covering a whole week of five minute values using an average of the totals from the last three months.
How would I accomplish this with Entity Framework?

Assuming your log is really exact on the "five mintues", and that I understood well
, you want a list with 7 day * 24 hours * (60/5) minutes, so 2016 results ?
//define a startDate
var beginningDate = <the date 3 month ago to start with>;
//get the endDate
var endDate = beginningDate.AddMonths(3);
var list = myTable.Where(m => m.TimeMark >= beginningDate && m.TimeMark <=endDate)
//group by dayofWeek, hour and minute will give you data for each distinct day of week, hour and minutes
.GroupBy(m => new {
dayofWeek = SqlFunctions.DatePart("weekday", m.TimeMark),
hour = SqlFunction.DatePart("hour", m.TimeMark),
minute = SqlFunctions.DatePart("minute", m.TimeMark)
})
.Select(g => new {
g.Key.dayofWeek,
g.Key.hour,
g.Key.minute,
total = g.Average(x => x.Total)
});

Related

How to make zero counts show in LINQ query when getting daily counts?

I have a database table with a datetime column and I simply want to count how many records per day going back 3 months. I am currently using this query:
var minDate = DateTime.Now.AddMonths(-3);
var stats = from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
};
That works fine, but the problem is that if there are no records for a day then that day is not in the results at all. For example:
3/21/2008 = 5
3/22/2008 = 2
3/24/2008 = 7
In that short example I want to make 3/23/2008 = 0. In the real query all zeros should show between 3 months ago and today.
Fabricating missing data is not straightforward in SQL. I would recommend getting the data that is in SQL, then joining it to an in-memory list of all relevant dates:
var stats = (from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
}).ToList(); // hydrate so we only query the DB once
var firstDate = stats.Min(s => s.date);
var lastDate = stats.Max(s => s.date);
var allDates = Enumerable.Range(1,(lastDate - firstDate).Days)
.Select(i => firstDate.AddDays(i-1));
stats = (from d in allDates
join s in stats
on d equals s.date into dates
from ds in dates.DefaultIfEmpty()
select new {
date = d,
count = ds == null ? 0 : ds.count
}).ToList();
You could also get a list of dates not in the data and concatenate them.
I agree with #D Stanley's answer but want to throw an additional consideration into the mix. What are you doing with this data? Is it getting processed by the caller? Is it rendered in a UI? Is it getting transferred over a network?
Consider the size of the data. Why do you need to have the gaps filled in? If it is known to be returning over a network for instance, I'd advise against filling in the gaps. All you're doing is increasing the data size. This has to be serialised, transferred, then deserialised.
If you are going to loop the data to render in a UI, then why do you need the gaps? Why not implement the loop from min date to max date (like D Stanley's join) then place a default when no value is found.
If you ARE transferring over a network and you still NEED a single collection, consider applying D Stanley's resolution on the other side of the wire.
Just things to consider...

multiple group by using linq

I need return just 2 lines in my query. One line with a string Today and a number of cases closed today, on my second line I need a string Last Week and a number of cases closed on the last week.
How I group with a range date?
Sum Name
----------- ----------
12 Today
33 Last Weeb
How about this:
var caseCounts = Cases
.Where(c => c.Date == today || (c.Date >= startOfLastWeek && c.Date <= endOfLastWeek))
.GroupBy(c => c.Date == today ? "Today" : "Last Week")
.Select(g => new {
Name = g.Key, Sum = g.Count()
});
You would need to define the 3 dates (today, startOfLastWeek, endOfLastWeek) before hand, but it gives you the results you are after.
GROUP BY YEARWEEK(date) should work. Depending on your dbms, you might be able to use another function, or program your own.
http://www.tutorialspoint.com/sql/sql-date-functions.htm#function_yearweek

LINQ - how to get next three business days excluding dates in table?

I need to find the next three available business days for a scheduling application. What is available depends upon excluded dates in a table. So...as long as the date is not in my table and not a Saturday or Sunday, I want the next three. I'd like to find an efficient way of doing this.
I need to return a List<DateTime>. The table is simple - ExcludedDates has an ID and a DateTime with the excluded date.
I'd like to have a single LINQ query expression but can't figure it out...thanks to all in advance and I apologize if this is trivial or obvious - it isn't to me.
Try this...
DateTime start = DateTime.Now.Date;
var result = Enumerable.Range(1, 10) // make this '10' higher if necessary (I assume you only exclude non-workingdays like Christmas and Easter)
.Select(offset => start.AddDays(offset))
.Where(date => !( date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek== DayOfWeek.Sunday))
.Where(d=> !exceptionTable.Any(date => date == d))
.Take(3).ToList();
List<DateTime> result = (from i in Enumerable.Range(1, excludeTable.Rows.Count + 6)
let date = inputDate.AddDays(i)
where date.DayOfWeek != DayOfWeek.Saturday &&
date.DayOfWeek != DayOfWeek.Sunday &&
!excludeTable.Rows.Cast<DataRow>().Select(r => (DateTime) r["ExcludeDate"]).Contains(date)
select date).Take(3).ToList();
excludeTable.Rows.Count + 6 is to cover the worst case where you skip over every thing in the excludeTable and then you have to skip over another weekend.
This assumes that a month will be reasonable depending on your excluded dates.
DateTime date = DateTime.Today;
// first generate all dates in the month of 'date'
var dates = Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month)).Select(n => new DateTime(date.Year, date.Month, n));
// then filter the only the start of weeks
var results = (from d in dates
where d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Saturday && !excludes.Any(i => i.DateTime.Date == d.Date) && date < d
select d).Take(3);
var excludedList = new List<long>() { DateTime.Parse("2011-07-27").Ticks };
var week = new List<long>(){
DateTime.Now.Date.Ticks,
DateTime.Now.Date.AddDays(1).Ticks,
DateTime.Now.Date.AddDays(2).Ticks,
DateTime.Now.Date.AddDays(3).Ticks,
DateTime.Now.Date.AddDays(4).Ticks,
DateTime.Now.Date.AddDays(5).Ticks,
DateTime.Now.Date.AddDays(6).Ticks,
DateTime.Now.Date.AddDays(7).Ticks,
DateTime.Now.Date.AddDays(8).Ticks
};
var available = (from d in week.Except(excludedList)
where new DateTime(d).DayOfWeek != DayOfWeek.Saturday && new DateTime(d).DayOfWeek != DayOfWeek.Sunday
select new DateTime(d)).Take(3);
foreach (var a in available)
Console.WriteLine(a.ToString());

How to get sum in nested collection via LINQ

class YearlyData
{
MonthlyData[] monthlyData = new MonthlyData[12];
}
class MonthlyData
{
int Salary;
}
Given I have a List<YearlyData>, how can I find total salary for a given month for the number of years.
Example for three years I need total salary given for 1st month & subsequent months.
If you want the sum of all the Salaries for April:
List<YearlyData> l;
l.Sum(yd => yd.monthlyData[3].Salary);
If I misread your question, and you really want all the salaries for April & subsequent months (in the year)
l.Sum(yd => yd.monthlyData.Skip(3).Sum());
Something like this:
list.SelectMany(x => x.monthlyData
.Select((m, i) => new {month = i+1,
data = m.Salary}
))
.GroupBy(x => x.month)
.Select(x => new {month = x.Key,
total = x.Sum(m => m.data)});
This will give you a list with twelve entries, one for each month along with the total amount of that month.

Finding minimum time from record and splitting it in LINQ

We have a database of swimmers with their times.
To create a ranking we want to get the fastest time of each athlete.
var rankings = (
from r in _db.Results
orderby r.swimtime
group r by r.athleteid into rg
select new
{
AthleteId = rg.Key,
FirstName = rg.Min(f2 => f2.Athlete.firstname),
Swimtime = rg.Min(f8 => f8.swimtime),
hours = rg.Min(f9 => f9.swimtime.Hours),
minutes = rg.Min(f10 => ("00" + f10.swimtime.Minutes.ToString()).Substring(("00" + f10.swimtime.Minutes.ToString()).Length - 2)), // to get 2 digits in minutes
seconds = rg.Min(f11 => ("00" + f11.swimtime.Seconds.ToString()).Substring(("00" + f11.swimtime.Seconds.ToString()).Length - 2)), // to get 2 digits in seconds
milliseconds = rg.Min(f12 => (f12.swimtime.Milliseconds.ToString() + "00").Substring(0, 2)), // because miliseconds are not always filled
}
);
Now the ranking is created correctly, however the time shown is not.
I know what the problem is, but don't know how to fix it:
In the database we have a swimmer that has 2 times : 00:01:02:10 (1min2sec10) and 00:00:56:95 (56sec95)
The result we get is the minimum for the minutes (=00), the minimum for the seconds (=02) and the minimum for the milliseconds (=10)
Resulting in a time of 00:00:02:10.
What we should get is the hours,minutes,seconds and milliseconds of the fastest time (=00:00:56:95)
Anyone any ideas on how to fix this ?
This should do the trick:
from result in db.Results
group result by result.AthleteId into g
let bestResult = (
from athleteResult in g
orderby athleteResult.SwimTime
select athleteResult).First()
orderby bestResult.SwimTime
select new
{
AthleteId = bestResult.Athlete.Id,
FirstName = bestResult.Athlete.FirstName,
BestTime = bestResult.SwimTime,
}
The query fetches the best result from a group (all results from a single athelete), orders by that result, and uses that result to populate the final result.
Stay away from calling .First on groups, as that can cause automatic requerying due to the difference between LINQ's group (key and elements) vs SQL's group (key and aggregates).
Instead, get the minSwimTime once and let it be.
var rankings =
from r in _db.Results
group r by r.athleteid into rg
let minSwimTime = rg.Min(x => x.swimtime)
select new
{
AthleteId = rg.Key,
FirstName = rg.Min(f2 => f2.Athlete.firstname),
Swimtime = minSwimTime,
hours = minSwimTime.Hours,
minutes = ("00" + minSwimTime.Minutes.ToString()).Substring(("00" + minSwimTime.Minutes.ToString()).Length - 2), // to get 2 digits in minutes
seconds = ("00" + minSwimTime.Seconds.ToString()).Substring(("00" + minSwimTime.Seconds.ToString()).Length - 2), // to get 2 digits in seconds
milliseconds = minSwimTime.Milliseconds.ToString() + "00").Substring(0, 2), // because miliseconds are not always filled
};
Also - don't do string formatting in the database. Database servers have better things to do than turn datetimes into text.

Resources