Linq - Get Max date from resultset - c#

I need to convert the following SQL query to Linq :-
SELECT CODE,SCODE,MAX(SDATE) AS SDATE FROM SHIFTSCHEDULE
WHERE COMPANY = 'ABC'
GROUP BY CODE,SCODE
ORDER BY MAX(SDATE)
DESC
So far, I have tried this :-
var data = ctx.ShiftSchedule.Where(m =>
m.Company == company && m.EmployeeId == item.EmployeeId
)
.GroupBy(m =>
new
{
m.EmployeeId,
m.ShiftId
})
.Select(m =>
new
{
EmployeeId = m.Key.EmployeeId,
ShiftCode = m.Key.ShiftId,
ShiftDate = m.Max(gg => gg.ShiftDate)
}).ToList();
The results i get are :-
Now what i want is to get record or item in this result set which is MaxDate. In the above image the MaxDate is 1st record.
How to get the MAXDATE from the resultset?

This should work:-
var data = ctx.ShiftSchedule.Where(x => x.Company == company
&& x.EmployeeId == item.EmployeeId)
.GroupBy(x => new { x.CODE, x.SCODE })
.Select(x => new
{
CODE = x.Key.CODE,
SCODE = x.Key.SCODE,
SDATE = x.Max(z => z.SDATE)
})
.OrderByDescending(x => x.SDATE).FirstOrDefault();
You can order the resulting collection and fetch the first object using FirstOrDefault.
If you want just MAXDATE, you can only project that.

Just add .OrderByDescending(x => x.ShiftDate).First(); at the end.
OrderByDescending date and then take .First()
var data = ctx.ShiftSchedule.Where(m =>
m.Company == company && m.EmployeeId == item.EmployeeId
)
.GroupBy(m =>
new
{
m.EmployeeId,
m.ShiftId
})
.Select(m =>
new
{
EmployeeId = m.Key.EmployeeId,
ShiftCode = m.Key.ShiftId,
ShiftDate = m.Max(gg => gg.ShiftDate)
}).ToList().OrderByDescending(x => x.ShiftDate).First();

Related

Mysql to Linq query

MySQL Query
select MAX(os.aggregate_date) as lastMonthDay,os.totalYTD
from (SELECT aggregate_date,Sum(YTD) AS totalYTD
FROM tbl_aggregated_tables
WHERE subscription_type = 'Subcription Income'
GROUP BY aggregate_date) as os
GROUP by MONTH(os.aggregate_date),YEAR(os.aggregate_date)
ORDER BY lastMonthDay;
converted to this LINQ query
var income = context.tbl_aggregated_tables
.Where(s => s.subscription_type == "Subcription Income")
.GroupBy(s => s.aggregate_date)
.Select(result => new
{
date = result.Key,
ytdsum = result.Select(x => x.YTD).Sum()
})
.GroupBy(s => new { month = s.date.Month, year = s.date.Year })
.Select(
// select max data and take its ytdsum value
).ToList();
The purpose of second grouping is to find the max day of each month with a year.
Now, How to select the max date of each month and its ytdsum after the second Grouping?
update
income = context.tbl_aggregated_tables
.Where(s => s.subscription_type == "Subcription Income")
.GroupBy(s => s.aggregate_date)
.Select(result => new
{
date = result.Key,
ytdsum = result.Select(x => x.YTD).Sum()
})
.GroupBy(s => new { s.date.Month, s.date.Year })
.Select(
x => x.Max(s => s.date)
).ToList()
this way it's only return the dates and i could not return the full object of the list including ytdSum.
This should work:
var income = context.tbl_aggregated_tables
.Where(s => s.subscription_type == "Subcription Income")
.GroupBy(s => s.aggregate_date)
.Select(result => new
{
date = result.Key,
ytdsum = result.Select(x => x.YTD).Sum()
})
.GroupBy(s => new { month = s.date.Month, year = s.date.Year })
.Select(
x => x.OrderByDescending(k => k.date).First()
).ToList();
income now is a list of objects, each of them have date and ytdsum
this worked good but it takes much more time than the original mysql query.
var income = context.tbl_aggregated_tables
.Where(s => s.subscription_type == "Subcription Income"
)
.GroupBy(s => s.aggregate_date)
.Select(result => new
{
date = result.Key,
ytdsum = result.Select(x => x.YTD).Sum()
})
.GroupBy(s => new { s.date.Month, s.date.Year })
.Select(
x => x.OrderByDescending(s => s.date)
)
.ToList()
.Select(el => el.FirstOrDefault())
.OrderBy(s=>s.date);

Optimize linq query by storing value in select

I have problem with linq query. In Select I am getting the same item twice which makes code execution much longer than I can afford. Is there any way to store x.OrderByDescending(z => z.Date).FirstOrDefault() item inside Select query?
Execution time: 180 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
})
.ToList();
Execution time: 1200 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
LastDate = x.OrderByDescending(z => z.Date).FirstOrDefault().Date,
})
.ToList();
Execution time: 2400 ms
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new HistoryReportItem
{
AccountNo = x.FirstOrDefault().AccountNo,
BankName = x.FirstOrDefault().BankName,
IsActive = x.FirstOrDefault().IncludeInCheck,
LastDate = x.OrderByDescending(z => z.Date).FirstOrDefault().Date,
DataItemsCount = x.OrderByDescending(z => z.Date).FirstOrDefault().CountItemsSend
})
.ToList();
You can try doing the select in two steps:
var groups = dataContext.History
.GroupBy(a => new { a.BankName, a.AccountNo })
.Select(x => new
{
first = x.FirstOrDefault();
lastDate = x.OrderByDescending(z => z.Date).FirstOrDefault();
}
.Select(x => new HistoryReportItem
{
AccountNo = x.first.AccountNo,
BankName = x.first.BankName,
IsActive = x.first.IncludeInCheck,
LastDate = x.lastDate.Date,
DataItemsCount = x.lastDate.CountItemsSend
})
.ToList();
If this fails, it might be because the engine can't convert it completely to SQL, and you can try adding an AsEnumerable() between the two Selects.

LINQ query to retrieve pivoted data taking too long

I am working on a LINQ query which includes some pivot data as below
var q = data.GroupBy(x => new
{
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g =>
new SummaryDto
{
Name= g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = g.Where(z => z.ProcessDate.Year == 2012).Count(),
Data2013 = g.Where(z => z.ProcessDate.Year == 2013).Count(),
Data2014 = g.Where(z => z.ProcessDate.Year == 2014).Count(),
Data2015 = g.Where(z => z.ProcessDate.Year == 2015).Count(),
Data2016 = g.Where(z => z.ProcessDate.Year == 2016).Count(),
Data2017 = g.Where(z => z.ProcessDate.Year == 2017).Count(),
TotalCount = g.Count(),
}).AsQueryable();
return q;
The above LINQ takes too long as it queries grp q.Count()*6 times. If there are 10000 records, then it queries 60000 times
Is there a better way to make this faster?
Add year to the group key, then group again, and harvest per-group counts:
return data.GroupBy(x => new {
x.Med.Name
, x.Med.GenericName
, x.ProcessDate.Year
}).Select(g => new {
g.Key.Name
, g.Key.GenericName
, g.Key.Year
, Count = g.Count()
}).GroupBy(g => new {
g.Name
, g.GenericName
}).Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, Data2012 = g.SingleOrDefault(x => x.Year == 2012)?.Count ?? 0
, Data2013 = g.SingleOrDefault(x => x.Year == 2013)?.Count ?? 0
, Data2014 = g.SingleOrDefault(x => x.Year == 2014)?.Count ?? 0
, Data2015 = g.SingleOrDefault(x => x.Year == 2015)?.Count ?? 0
, Data2016 = g.SingleOrDefault(x => x.Year == 2016)?.Count ?? 0
, Data2017 = g.SingleOrDefault(x => x.Year == 2017)?.Count ?? 0
, TotalCount = g.Sum(x => x.Count)
}).AsQueryable();
Note: This approach is problematic, because year is hard-coded in the SummaryDto class. You would be better off passing your DTO constructor an IDictionary<int,int> with counts for each year. If you make this change, the final Select(...) would look like this:
.Select(g => new SummaryDto {
Name = g.Key.Name
, GenericName = g.Key.GenericName
, TotalCount = g.Sum(x => x.Count)
, DataByYear = g.ToDictionary(i => i.Year, i => i.Count)
}).AsQueryable();
I suggest grouping inside the group by year and then converting to a dictionary to access the counts. Whether it is faster to group with year first and then count in-memory depends on the distribution of the initial grouping, but with the database it may depend on how efficiently it can group by year, so I would test to determine which seems fastest.
In any case grouping by year after the initial grouping is about 33% faster than your query in-memory, but again it is vastly dependent on the distribution. As the number of initial groups increase, the grouping by Year queries slow down to match the original query. Note that the original query without any year counts is about 1/3 the time.
Here is grouping after the database grouping:
var q = data.GroupBy(x => new {
x.Med.Name,
x.Med.GenericName,
}).ToList().Select(g => {
var gg = g.GroupBy(d => d.ProcessDate.Year).ToDictionary(d => d.Key, d => d.Count());
return new SummaryDto {
Name = g.Key.Name,
GenericName = g.Key.GenericName,
Data2012 = gg.GetValueOrDefault(2012),
Data2013 = gg.GetValueOrDefault(2013),
Data2014 = gg.GetValueOrDefault(2014),
Data2015 = gg.GetValueOrDefault(2015),
Data2016 = gg.GetValueOrDefault(2016),
Data2017 = gg.GetValueOrDefault(2017),
TotalCount = g.Count(),
};
}).AsQueryable();

The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities

var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId,
dayState = r.o.day_desc.Split('[', ']')[1]
}).ToList();
I get an exception :
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to
Entities
How i could find an alternative to Split('[', ']')[1] in this query
You must commit the query and do the split after loading the data:
var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId = r.sh.recordId,
dayState = r.o.day_desc,
})
.ToList()//Here we commit the query and load data
.Select(x=> {
var parts = x.dayState.Split('[', ']');
return new {
x.dayDate,
x.empNum,
x.empName,
x.shiftId,
x.shiftName,
x.recordId,
dayState = parts.Length > 1 ?parts[1]:"",
};
})
.ToList();
I had this Issue and the approach that I've chose was that get all element I wanted and save them into a List and then filter the actual data on that list.
I know this is not the best answer but it worked for me.

c# lambda reading each row with GROUP BY and SUM

This is the working query i was using in my management studio.
SELECT TOP 5 productCode, SUM(productSales) AS sales
FROM sellingLog
WHERE (salesYear = '2014')
GROUP BY productCode
ORDER BY sales DESC
I want to convert the query above into lambda, but i can't seems to make it works. the lambda still lacks of order by and select the productCode
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(b => b.Sum(u => u.productSales)).Take(5)
.ToList();
foreach(var v in topProduct)
{
//reading 'productCode' and 'sales' from each row
}
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(g => new { productCode = g.Key, sales = g.Sum(u => u.productSales) })
.OrderByDescending(x => x.productCode)
.Take(5)
.ToList();
You can use the .Key with group by to get productCode
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(b => new {u.Key, b.Sum(u => u.productSales)}).Take(5)
.OrderByDescending(b=>b.Sales)
.ToList();

Categories