I'm struggling to find an example of how to return a conditional sum using a LINQ query or LAMBDA. I've written both independently but combining the CASE with SUM is vexing. I'm tempted to "cheat" and use a SQL view, but thought I'd ask first. I greatly appreciate any suggestions. Here's my SQL that I'm looking to convert.
SELECT p.product_name,
SUM(CASE WHEN o.order_dt <= getdate() - 1 THEN o.quantity END) AS volume_1day,
SUM(CASE WHEN o.order_dt <= getdate() - 7 THEN o.quantity END) AS volume_7day,
SUM(CASE WHEN o.order_dt <= getdate() - 30 THEN o.quantity END) AS volume_30day,
SUM(o.quantity) AS volume_all
FROM products p left outer join orders o on p.product_id = o.product_id
GROUP BY p.product_name
Here is an example using the Northwinds database. This will get you the results that you are expecting but the SQL won't match your example.
using (var context = new NorthwindEntities())
{
DateTime volumn1Date = DateTime.Today.AddDays(-1);
DateTime volumn7Date = DateTime.Today.AddDays(-7);
DateTime volumn30Date = DateTime.Today.AddDays(-30);
var query = from o in context.Order_Details
group o by o.Product.ProductName into g
select new
{
ProductName = g.Key,
Volume1Day = g.Where(d => d.Order.OrderDate.Value <= volumn1Date)
// cast to Int32? because if no records are found the result will be a null
.Sum(d => (Int32?) d.Quantity),
Volume7Day = g.Where(d => d.Order.OrderDate.Value <= volumn7Date)
.Sum(d => (Int32?) d.Quantity),
Volume30Day = g.Where(d => d.Order.OrderDate.Value <= volumn30Date)
.Sum(d => (Int32?) d.Quantity)
};
var list = query.ToList();
}
answer does not wrong but does not generate optimize query. better answer is:
using (var context = new NorthwindEntities())
{
DateTime volumn1Date = DateTime.Today.AddDays(-1);
DateTime volumn7Date = DateTime.Today.AddDays(-7);
DateTime volumn30Date = DateTime.Today.AddDays(-30);
var query = from o in context.Order_Details
group o by o.Product.ProductName into g
select new
{
ProductName = g.Key,
Volume1Day = g.Sum(d => d.Order.OrderDate.Value <= volumn1Date ? (Int32?) d.Quantity : 0),
Volume7Day = g.Sum(d => d.Order.OrderDate.Value <= volumn7Date ? (Int32?) d.Quantity : 0),
Volume30Day = g.Sum(d => d.Order.OrderDate.Value <= volumn30Date ? (Int32?) d.Quantity : 0)
};
var list = query.ToList();
}
Related
I am so sorry from the question, but I can not take a period from a DateTime. for exemple: If I have date "10.10.2016 7:00", 10.10.2016 10:00", I need to take only the rows with the time between "6:00" and "8:00". Next is my code by return an error : "can not use TimeOfDay ",help me please
ds.TrafficJamMorning = (from row in orderQuery
where row.AcceptedTime.TimeOfDay >= new TimeSpan(6, 30, 0) &&
row.AcceptedTime.TimeOfDay <= new TimeSpan(9, 30, 0)
group row by row.AcceptedTime.Date
into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
TimeOfDay Is not supported by the linq provider and it does not know how to parse it into sql. Use instead DbFunctions.CreateTime:
Also instantiate the timespans before the linq query so you do not instantiate a new object every time
var startTime = new TimeSpan(6, 30, 0);
var endTime = new TimeSpan(9, 30, 0);
var result = (from row in orderQuery
let time = DbFunctions.CreateTime(row.AcceptedTime.Hour, row.AcceptedTime.Minute, row.AcceptedTime.Second)
where time >= startTime &&
time <= endTime
group row by DbFunctions.TruncateTime(row.AcceptedTime) into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
Looking again at the question - If all you want to check is that it is between 2 hours then use the Hour property (This won't be nice to write if you want to check for example Hour and Minues and in that case I'd go for my first suggestion):
var result = (from row in orderQuery
where row.AcceptedTime.Hour >= 6
row.AcceptedTime.Hour < 8
group row by DbFunctions.TruncateTime(row.AcceptedTime) into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
I use the following where clause on my IQueryable:
var query = dbContext.GetAllItems().AsQueryable();
//... other filters
if(MusBeBetween6and8){
query = query.Where(item => item.AcceptedTime.Hour > 6 && item.AcceptedTime.Hour < 8);
}
//... other filters
return query.ToList();
Hope it helps. This also works for Oracle + Odac.
ds.TrafficJamMorning = (from row in orderQuery
where
DbFunctions.DiffMinutes( DbFunctions.TruncateTime(row.AcceptedTime), row.AcceptedTime) >= 6 * 60 + 30 &&
DbFunctions.DiffMinutes( DbFunctions.TruncateTime(row.AcceptedTime), row.AcceptedTime) <= 9 * 60 + 30
group row by DbFunctions.TruncateTime(row.AcceptedTime)
into grp
select new TrafficJamPeriodInfo
{
CurrentDateTime = grp.Key,
ReceptionCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Reception),
InternetCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.Internet),
ExchangeSystemCount = grp.Count(r => r.OrderOriginId == (int)OrderOrigin.ExchangeSystem)
}).ToList();
I had a similar problem.
You can compare the date parts instead.
where row.Year > s.Year && r.Month > s.Month && row.Day > s.Day
How can I do this correctly?
This is failing because schedule-on does not exist within m from RR2.
var RR = (from m in DataContext.csrcallds
where m.scheduledon >= earliestDate
&& m.scheduledon <= objdate1.DateStart
&& m.assignto == 113
&& (SqlMethods.DateDiffDay(m.scheduledon, m.completedon) > 5)
select m
);
var RR2 = RR.Select(x => (GetBusinessDays((DateTime)x.scheduledon, (DateTime)x.completedon)) > 5).ToList());
var RnR = (from m in RR2
group m by new { m.scheduledon.Value.Year, m.scheduledon.Value.Month } into p
orderby p.Key.Year ascending, p.Key.Month ascending
select new Date1()
{
theDate = DateTime.Parse($"{p.Key.Month} - {p.Key.Year}"),
theCount = p.Count(),
theString = $"{p.Key.Month} - {p.Key.Year}"
});
I am trying to query all the results.
Then use my GetBusinessDay function to filter out the ones I don't need, gathering only the records within 5 business days.
Then put the results into my Model named Date1.
I'm trying to do it like this because I cannot use GetBusinessDays within an LINQ query.
So I'm trying to filter it outside of SQL, then group the records again.
How do I go about accomplishing this task?
You could add this to your SQL Query to filter out the weekend days.
SELECT *
FROM your_table
WHERE ((DATEPART(dw, date_created) + ##DATEFIRST) % 7) NOT IN (0, 1)
Or This
select [date_created]
from table
where DATENAME(WEEKDAY, [date_created]) <> 'Saturday'
and DATENAME(WEEKDAY, [date_created]) <> 'Sunday'
Or if you have to stick to LINQ try the ideas outlined here:
Linq query DateTime.Date.DayOfWeek
DateTime firstSunday = new DateTime(1753, 1, 7);
var bookings = from b in this.db.Bookings
where EntityFunctions.DiffDays(firstSunday, b.StartDateTime) % 7 == 1
select b;
Solved by using function workdays server side:
https://stackoverflow.com/a/252532/6157660
Allows me to make a simple LINQ query, and gives me what I need.
I did edit the function to remove the +1 from DateDiff. as same days should be 0 not 1.
Thank you guys for your help!
var RnR = (from m in DataContext.csrcallds
where m.scheduledon >= earliestDate
&& m.scheduledon <= objdate1.DateStart
&& m.assignto == 113
&& (DataContext.fn_WorkDays((DateTime)m.scheduledon, (DateTime)m.completedon)) > 5
group m by new { m.scheduledon.Value.Year, m.scheduledon.Value.Month } into p
orderby p.Key.Year ascending, p.Key.Month ascending
select new Date1()
{
theDate = DateTime.Parse($"{p.Key.Month} - {p.Key.Year}"),
theCount = p.Count(),
theString = $"{p.Key.Month} - {p.Key.Year}"
});
First I want to say hello, I'm new to this site ;-)
My problem is to transform the following sql-query into a c# linq-query.
( I HAVE searched hard for an existing answer but I'm not able to combine the solution for
the joins on multiple conditions and the grouping / counting ! )
The sql-query :
DECLARE #datestart AS DATETIME
DECLARE #dateend AS DATETIME
SET #datestart = '01.04.2014'
SET #dateend = '30.04.2014'
SELECT md1.value AS [controller],md2.value AS [action], COUNT(md2.value) AS [accesscount], MAX(re.TIMESTAMP) AS [lastaccess] FROM recorderentries AS re
INNER JOIN messagedataentries AS md1 ON re.ID = md1.recorderentry_id AND md1.position = 0
INNER JOIN messagedataentries AS md2 ON re.ID = md2.recorderentry_id AND md2.position = 1
WHERE re.TIMESTAMP >= #datestart AND re.TIMESTAMP <= #dateend
AND re.messageid IN ('ID-01','ID-02' )
GROUP BY md1.value,md2.value
ORDER BY [accesscount] DESC
Any suggestions are welcome ...
What i have so far is this :
var _RecorderActionCalls = (from r in _DBContext.RecorderEntries
join m1 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m1.ID,
b = m1.Position
}
join m2 in _DBContext.MessageDataEntries on
new {
a = r.ID,
b = 0
} equals new {
a = m2.ID,
b = m2.Position
}
where r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01")
group r by new { md1 = m1.Value, md2 = m2.Value } into r1
select new { controller = r1.Key.md1, action = r1.Key.md2, count = r1.Key.md2.Count() }).ToList();
But this throws an exception ( translated from german ) :
DbExpressionBinding requires an input expression with a Listing Result Type ...
UPDATE : Back with headache ... ;-)
I found a solution to my problem :
var _RecorderActionCalls = _DBContext.RecorderEntries
.Where(r => r.TimeStamp >= StartDate & r.TimeStamp <= EndDate & (r.MessageID == "VAREC_100_01" | r.MessageID == "VAAUTH-100.01"))
.GroupBy(g => new { key1 = g.MessageData.FirstOrDefault(md1 => md1.Position == 0).Value, key2 = g.MessageData.FirstOrDefault(md2 => md2.Position == 1).Value })
.Select(s => new {
ControllerAction = s.Key.key1 + " - " + s.Key.key2,
Value = s.Count(),
Last = s.Max(d => d.TimeStamp)
}).ToList();
With this syntax it works for me. Thank you for thinking for me :-)
Something like that:
List<string> messageIdList = new List<string> { "ID-01", "ID-02" };
from re in RecorderEntries
from md1 in MessageDataEntries
from md2 in MessageDataEntries
where re.ID = md1.recorderEntry_id && md1.position == 0
where re.ID = md2.recorderEntry_id && md2.position == 1
where idList.Contains(re.messageid)
let joined = new { re, md1, md2 }
group joined by new { controller = joined.md1.value, action = joined.md2.value } into grouped
select new {
controller = grouped.Key.controller,
action = grouped.Key.action,
accesscount = grouped.Where(x => x.md2.value != null).Count(),
lastaccess = grouped.Max(x => x.re.TimeStamp) }
How can I write the following LEFT OUTER JOIN SQL query against my Calendar and Sales tables for the purpose of grouping summed sales by day, week or month in LINQ so that it can be materialised by LINQ-to-SQL?
SELECT c.CalendarDate, c.FirstDayOfWeek, c.FirstDayOfMonth,
ISNULL(s.Total, 0) as Total
FROM Calendar as c
LEFT OUTER JOIN Sales as s
on s.SaleDate >= c.CalendarDateTime and
s.SaleDate < c.NextDayDateTime
WHERE s.SaleDate BETWEEN #since and #until
I managed to get an inner join working in LINQ, but I need an outer join to retrieve days with zero sales. Here is the code I use for an inner join:
var sales = from s in db.Sales
from c in db.Calendars
where
s.SaleDate >= c.CalendarDate && s.SaleDate < c.NextDayDateTime
&& s.SaleDate >= sinceDate && s.SaleDate < dateEnd
select new
{
c.CalendarDate,
c.FirstDateOfWeek,
c.FirstDateOfMonth,
s.Total
};
I can then switch on a date interval and group sales as follows:
Daily:
var groupedSales = sales.GroupBy(x => x.CalendarDate);
Weekly:
var groupedSales = sales.GroupBy(x => x.FirstDateOfWeek);
Monthly:
var groupedSales = sales.GroupBy(x => x.FirstDateOfMonth);
Finally:
var salesReport = from g in groupedSales
orderby g.Key
select new {
Date = g.Key,
Total = g.Sum(x => x.Total)
};
Alternatively, it could also work to inject zero sale records into my report after retrieving sales for non-zero days only.
What about this?
var sales = from s in db.Sales
from c in db.Calendars.DefaultIfEmpty()
where
s.SaleDate >= c.CalendarDate && s.SaleDate < c.NextDayDateTime
&& s.SaleDate >= sinceDate && s.SaleDate < dateEnd
EDIT:
You can create helper classes like this:
class CalendarSealesHelper
{
public DateTime CalendarDate {get; set;}
public DateTime NextDayDateTime {get; set;}
}
class CalendarSealesHelperComparer : IEqualityComparer<CalendarSealesHelper>
{
public bool Equals(CalendarSealesHelper c1, CalendarSealesHelper c2)
{
if (c2.CalendarDate >= c1.CalendarDate
&& c2.NextDayDateTime < c1.NextDayDateTime)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(CalendarSealesHelper c)
{
int hCode = (int)c.CalendarDate.Ticks ^ (int)c.NextDayDateTime.Ticks;
return hCode.GetHashCode();
}
}
Then try this:
var query = db.Calendars.GroupJoin(
db.Sales,
c => new CalendarSealesHelper{c.CalendarDate, c.NextDayDateTime},
s => new CalendarSealesHelper{s.SaleDate, s.SaleDate},
(c, s) => new {Calendars = c, Sales = s},
new CalendarSealesHelperComparer())
.SelectMany(s => s.Sales.DefaultIfEmpty(),
(c, s) => new {
CalendarDate = c.CalendarDate,
FirstDayOfWeek = c.FirstDayOfWeek,
FirstDayOfMonth = c.FirstDayOfMonth,
Total = s.Total,
SaleDate = s.SaleDate
})
.Where(r => r.SaleDate >= sinceDate && r.SaleDate <= dateEnd);
I am looking for some help on adapting the following LINQ query to return all dates within the next 6 months, even those where no records fall within the given month.
var maxDate = DateTime.Now.AddMonths(6);
var orders = (from ord in db.Items
where (ord.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
group ord by new
{
ord.Expiry.Value.Year,
ord.Expiry.Value.Month
}
into g
select new ExpiriesOwnedModel
{
Month = g.Select(n => n.Expiry.Value.Month).First(),
Quantity = g.Count()
}).ToList();
I'd really appreciate any assistance or pointers on how best to implement this.
I'm not sure how well it'll interact with your database, but I'd do this as with a join:
var firstDaysOfMonths = Enumerable.Range(0, 7).Select(i =>
new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i));
var orders = firstDaysOfMonths.GroupJoin(
db.Items,
fd => fd,
ord => new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1),
(fd, ords) => new { Month = fd.Month, Quantity = ords.Count() });
Note you may end up with an extra month where before you didn't (on the first day of the month?)
Stolen from Rawling's answer, if you prefer query syntax for group joins (I do):
var orders =
from month in Enumerable.Range(0, 7)
.Select(i => new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(i))
join ord in db.Items
on month equals new DateTime(ord.Expiry.Value.Year, ord.Expiry.Value.Month, 1)
into ords
select new { month.Month, Quantity = ords.Count() };
Alternative if it does not play nice with the database:
var rawGroups = db.Items.Where(item.Expiry >= DateTime.Now && ord.Expiry <= maxDate)
.GroupBy(item => new
{
item.Expiry.Value.Year,
item.Expiry.Value.Month
}, g => new ExpiriesOwnedModel()
{
Month = g.Key.Month,
Quantity = g.Count()
}).ToDictionary(model => model.Month);
var result = Enumerable.Range(DateTime.Now.Month,6)
.Select(i => i > 12 ? i - 12 , i)
.Select(i => rawGroups.Keys.Contains(i) ?
rawGroups[i] :
new ExpiriesOwnedModel()
{ Month = i , Quantity = 0 });