Linq - Grouping by date and selecting count - c#

I am currently working through a problem where I would like to run a query which groups the results by the date selected.
For this example, imagine a simple model like so:
public class User
{
public DateTime LastLogIn {get; set;}
public string Name {get; set;}
}
The solution I am looking for is to get a count of Users logged in by Date.
In the database the DateTime are stored with both date and time components, but for this query I really only care about the date.
What I currently have is this:
context.Users
.Where((x.LastLogIn >= lastWeek)
&& (x.LastLogIn <= DateTime.Now))
.GroupBy(x => EntityFunctions.TruncateTime(x.LastLogIn))
.Select(x => new
{
Value = x.Count(),
Day = (DateTime)EntityFunctions.TruncateTime(x.Key)
}).ToList();
The above however returns an empty list.
End goal is to have a List of objects, which contain a Value (the count of users logged in on a day) and a Day (the day in question)
Any thoughts?
Upon changing the query to:
context.Users
.Where((x.LastLogIn >= lastWeek)
&& (x.LastLogIn <= DateTime.Now))
.GroupBy(x => EntityFunctions.TruncateTime(x.LastLogIn))
.Select(x => new
{
Value = x.Count(),
Day = (DateTime)x.Key
}).ToList();
it now returns a list with a single item, with the Value being the total count of Users that match the where clause, and the Day being the very first day. It still hasn't seemed to be able to group by the days
NOTE: turns out the above code is right, I was just doing something else wrong.
Sql that it is generating is (note might be very slight syntactical errors here with me adjusting it for the example):
SELECT
1 AS [C1],
[GroupBy1].[A1] AS [C2],
CAST( [GroupBy1].[K1] AS datetime2) AS [C3]
FROM ( SELECT
[Filter1].[K1] AS [K1],
COUNT([Filter1].[A1]) AS [A1]
FROM ( SELECT
convert (datetime2, convert(varchar(255), [Extent1].[LastLogIn], 102) , 102) AS [K1],
1 AS [A1]
FROM [dbo].[Users] AS [Extent1]
WHERE (([Extent1].[LastLogIn] >= #p__linq__1) AND ([Extent1].[LastLogIn] <= #p__linq__2)
) AS [Filter1]
GROUP BY [K1]
) AS [GroupBy1]

You do not need the second TruncateTime in there:
context.Users
.Where((x.LastLogIn >= lastWeek) && (x.LastLogIn <= DateTime.Now))
.GroupBy(x => DbFunctions.TruncateTime(x.LastLogIn))
.Select(x => new
{
Value = x.Count(),
// Replace the commented line
//Day = (DateTime)DbFunctions.TruncateTime(x.Key)
// ...with this line
Day = (DateTime)x.Key
}).ToList();
The GroupBy has truncated the time from the DateTime already, so you do not need to call it again.
To use DbFunctions.TruncateTime you'll need to reference the assembly System.Data.Entity and include using System.Data.Entity;
Note: Edited to address deprecation of EntityFunctions.

Try this:
.GroupBy(x => new {Year = x.LastLogIn.Year, Month = x.LastLogIn.Month, Day = x.LastLogIn.Day)
.Select(x => new
{
Value = x.Count(),
Year = x.Key.Year,
Month = x.Key.Month,
Day = x.Key.Day
})

You can do it easily:
yourDateList.GroupBy(i => i.ToString("yyyyMMdd"))
.Select(i => new
{
Date = DateTime.ParseExact(i.Key, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None),
Count = i.Count()
});

I came across this same problem to get a count based on group by a datetime column. This is what i did. Thanks for some of the answers here. I tried the minimal version and I used this code in .netcore solution.
var result = _context.yourdbmodel
.GroupBy(x=>x.yourdatetimecolumn.Value.Date)
.select(x=> new
{
Count = x.Count(),
Date = (DateTime)x.Key // or x.Key.Date (excluding time info) or x.Key.Date.ToString() (give only Date in string format)
})
.ToList();
Sample output:
[ {
"requestDate": "2020-04-01",
"requestCount": 3 }, {
"requestDate": "2020-04-07",
"requestCount": 14 } ]
Hope this helps someone in future.

You can also do it in one line.
var b = (from a in ctx.Candidates select a)
.GroupBy(x => x.CreatedTimestamp.Date).Select(g => new { Date=(g.Key).ToShortDateString(), Count = g.Count() });

Just convert first IQueryable to IEnumerable(List) with the help of ToList() then use groupby
context.Users
.Where((x.LastLogIn >= lastWeek) && (x.LastLogIn <= DateTime.Now))
.ToList()
.GroupBy(x => x.LastLogIn.Date))
.Select(x => new
{
Value = x.Count(),
Day = (DateTime)x.Key
}).ToList();

Related

SQL Server query with subquery to LINQ query

I wrote a SQL query that will get the count of tickets, closed tickets and its closure rate (%) and group it monthly basis (current year), but I would like to express this as a LINQ query to achieve the same result.
SELECT *, (ClosedCount * 100 / TicketCount) AS ClosureRate FROM (
SELECT COUNT(Id) as TicketCount, MONTH(InsertDate) as MonthNumber, DATENAME(MONTH, E1.InsertDate) as MonthName,
(SELECT COUNT(Id) FROM EvaluationHistoryTable E2 WHERE TicketStatus = 'CLOSED' AND YEAR(E2.InsertDate) = '2021') AS 'ClosedCount'
FROM EvaluationHistoryTable E1
WHERE YEAR(E1.InsertDate) = 2021
GROUP BY MONTH(InsertDate), DATENAME(MONTH, E1.InsertDate));
This is code that I'm working on:
var ytdClosureRateData = _context.EvaluationHistoryTable
.Where(t => t.InsertDate.Value.Year == DateTime.Now.Year)
.GroupBy(m => new
{
Month = m.InsertDate.Value.Month
})
.Select(g => new YtdTicketClosureRateModel
{
MonthName = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedMonthName(g.Key.Month),
MonthNumber = g.Key.Month,
ItemCount = g.Count(),
ClosedCount = // problem
ClosureRate = // problem
}).AsEnumerable()
.OrderBy(a => a.MonthNumber)
.ToList();
I am having rtouble trying to express the count of closed tickets (ClosedCount) in linq format, I need the count to calculate the ClosureRate.
This won't be the same SQL but it should produce the same result in memory:
var ytdClosureRateData = _context.EvaluationHistoryTable
.Where(t => t.InsertDate.Value.Year == DateTime.Now.Year)
.GroupBy(m => new
{
Month = m.InsertDate.Value.Month
})
.Select(g => new
{
Month = g.Key.Month,
ItemCount = g.Count(),
ClosedCount = g.Where(t => t.TicketStatus == "CLOSED").Count()
}).OrderBy(a => a.MonthNumber)
.ToList()
.Select(x => new YtdTicketClosureRateModel
{
MonthName = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedMonthName(x.Month),
MonthNumber = x.Month,
ItemCount = x.ItemCount,
ClosedCount = x.ClosedCount,
ClosureRate = x.ClosedCount * 100D / x.ItemCount
})
.ToList();
Two techniques have been implemented here:
Use Fluent Query to specify the filter to apply for the ClosedCount set, you can combine Fluent and Query syntax to your hearts content, they each have pros and cons, in this instance it just simplifed the syntax to do it this way.
Focus the DB query on only bringing back the data that you need, the rest can be easily calculated in member after the initial DB execution. That is why there are 2 projections here, the first should be expressed purely in SQL, the rest is evaluated as Linq to Objects
The general assumption is that traffic over the wire and serialization are generally the bottle necks for simple queries like this, so we force Linq to Entities (or Linq to SQL) to produce the smallest payload that is practical and build the rest or the values and calculations in memory.
UPDATE:
Svyatoslav Danyliv makes a really good point in this answer
The logic can be simplified, from both an SQL and LINQ perspective by using a CASE expression on the TicketStatus to return 1 or 0 and then we can simply sum that column, which means you can avoid a nested query and can simply join on the results.
Original query can be simplified to this one:
SELECT *,
(ClosedCount * 100 / TicketCount) AS ClosureRate
FROM (
SELECT
COUNT(Id) AS TicketCount,
MONTH(InsertDate) AS MonthNumber,
DATENAME(MONTH, E1.InsertDate) AS MonthName,
SUM(CASE WHEN TicketStatus = 'CLOSED' THEN 1 ELSE 0 END) AS 'ClosedCount'
FROM EvaluationHistoryTable E1
WHERE YEAR(E1.InsertDate) = 2021
GROUP BY MONTH(InsertDate), DATENAME(MONTH, E1.InsertDate));
Which is easily convertible to server-side LINQ:
var grouped =
from eh in _context.EvaluationHistoryTable
where eh.InsertDate.Value.Year == DateTime.Now.Year
group eh by new { eh.InsertDate.Value.Month }
select new
{
g.Key.Month,
ItemCount = g.Count(),
ClosedCount = g.Sum(t => t.TicketStatus == "CLOSED" ? 1 : 0)
};
var query =
from x in grouped
orderby x.Month
select new YtdTicketClosureRateModel
{
MonthName = DateTimeFormatInfo.CurrentInfo.GetAbbreviatedMonthName(x.Month),
MonthNumber = x.Month,
ItemCount = x.ItemCount,
ClosedCount = x.ClosedCount,
ClosureRate = x.ClosedCount * 100D / x.ItemCount
};
var result = query.ToList();

Grouping by day in LINQ without TruncateTime()

I have the following LINQ query for a MySQL database in a C# Razor MVC project.
private Dictionary<DateTime?, int> getOrderQuantityDict(DateTime start, DateTime end, int siteCode)
{
return (from o in thisDataEntities.this_table
where o.created_at >= start
&& o.created_at <= end
&& o.store_id == siteCode
select new { OrderDate = o.created_at, Id = o.entity_id})
.GroupBy(q => q.OrderDate)
.ToDictionary(q => q.Key, q => q.Count());
}
I need to group by day. Right now q.OrderDate has hours, minutes, and seconds. I need to ignore those when grouping.
The tricky part: I need to do this without TruncateTime(). When our host moved our DB, we lost the ability to use TruncateTime() for some reason. Our host has been less than helpful on this issue, and I'm hoping a workaround is possible.
Haven't tested it but the following may help you:
return (from o in thisDataEntities.this_table
where o.created_at >= start
&& o.created_at <= end
&& o.store_id == siteCode
select new { OrderDate = o.created_at, Id = o.entity_id})
.AsEnumerable() //Once this is executed, the database will return the result of the query and any other statement after this will be ran locally so TruncateTime will not be an issue
.GroupBy(q => q.OrderDate)
.ToDictionary(q => q.Key, q => q.Count());
You can convert date to the string and make grouping based on the string representation of date.
return
thisDataEntities.this_table
.Where(o => o.created_at >= start)
.Where(o => o.created_at <= end)
.Where(o => o.store_id == siteCode)
.Select(o => new
{
OrderDate = o.created_at,
Id = o.entity_id,
OrderDateFormatted =
SqlFunctions.DateName("yyyy", o.created_at) + "-" +
SqlFunctions.DateName("mm", o.created_at) + "-" +
SqlFunctions.DateName("dd", o.created_at)
})
.GroupBy(n => n.OrderDateFormatted) // format "2017-10-03"
.ToDictionary(g => g.First().OrderDate, g => g.Count());
With approach above execution should happened on database side. Of course only in case GroupBy supported.

selecting a single item from grouped linq query based on a condition

I am struggling with a grouping linq query
I have this:
DateTime dateFrom = new Date(2014,8,2);
var GroupedPrices = Prices.Where(p => p.ArrivalDateFrom <= dateFrom
&&
p.ArrivalDateTo > dateFrom)
.GroupBy(p => p.ItemID);
I am trying to get to a single price of each ID - eg. in/from each group, based on the newest ValidFrom date of that price.
I have started by grouping them by their ItemID's (not the individual price record id's) but am struggling in working out how to grab just a single one based on that newest ValidFrom date. I thought I could order them before grouping, but wasn't sure that would stick after the grouping was done.. ie
expecting it will use a Max(x=>x.ValidFrom) type thing or OrderByDescending(x=>x.ValidFrom).First() but cant work that out
any help much appreciated
thanks
I think you just need to select what you want, at the end, like so:
DateTime dateFrom = new Date(2014,8,2);
var GroupedPrices = Prices
.Where(p => p.ArrivalDateFrom <= dateFrom && p.ArrivalDateTo > dateFrom)
.GroupBy(p => p.ItemID)
.Select(g => new{ ItemId = g.Key, NewestPrice = g.OrderByDescending(p => p.ValidFrom).First() });

Implementing the min & max function with group by and having in linq

I have this below sql query which i have to implement the same using linq but i am unable to create a linq statement for the same. Please provide some help.
Declare #DateTime datetime
set #DateTime = '06/20/2012';-- To get the yesterday/any day attendance record.
With Cte
as
(
Select EmployeeiD,(convert(varchar,(DATEDIFF(MINUTE,Min(DateAndTime),Max(DateAndTime))/60)) + ':'
+ convert(varchar,(DATEDIFF(MINUTE,Min(DateAndTime),Max(DateAndTime))/60)))WorkedHoursAndMinutes,
Min(DateAndTime) As DateAndTime from EmployeeAttendance group by
EmployeeiD,convert(varchar,DateAndTime,101),convert(varchar,DateAndTime,101) having (
(DATEDIFF(MINUTE,Min(DateAndTime),Max(DateAndTime))/60) >= 12
or (DATEDIFF(MINUTE,Min(DateAndTime),Max(DateAndTime))) = 0 )
)
select * from Cte
where convert(varchar,Cte.DateAndTime,103) =
(select convert(varchar,#DateTime,103));
As you only show how you tried to achieve something it's kinda hard to exactly infer what you want. But I think this is it:
employeeAttendances.Where(e => e.DateAndTime >= d1 && e.DateAndTime < d2)
.GroupBy (e => e.EmployeeiD)
.Select (g => new
{
g.Key,
Diff = EntityFunctions.DiffHours(g.Min(x => x.DateAndTime),
g.Max(x => x.DateAndTime))
} )
.Where(a => a.Diff > 12 || a.Diff == 0)
It calculates the difference between the minimum and maximum time on one specific day for each EmployeeID.

Write a comparable LINQ query for aggregate distinct count in sql?

I want to get a count for each month but count should be only at most one per day even if there are multiple occurences . I have the SQL query which works right but having trouble to convert it into LINQ -
select
count(DISTINCT DAY(date)) as Monthly_Count,
MONTH(date) as Month,
YEAR(date)
from
activity
where
id=#id
group by
YEAR(date),
MONTH(date)
Could anyone help me translating the above query to LINQ. Thanks!
Per LINQ to SQL using GROUP BY and COUNT(DISTINCT) given by #Rick, this should work:
var query = from act in db.Activity
where act.Id == id
group act by new { act.Date.Year, act.Date.Month } into g
select new
{
MonthlyCount = g.Select(act => act.Date.Day).Distinct().Count(),
Month = g.Key.Month,
Year = g.Key.Year
};
I don't know if L2S can convert the inner g.Select(act => act.Date.Day).Distinct.Count() properly.
var results = db.activities.Where(a => a.id == myID)
.GroupBy(a => new
{
Month = a.date.Month,
Year = a.date.Year
})
.Select(g => new
{
Month = g.Key.Month,
Year = g.Key.Year,
Monthly_Count = g.Select(d => d.date.Day)
.Distinct()
.Count()
})

Categories

Resources