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);
Related
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) }
Trying to figure out why this method isn't returning the items within the timespan I specified with some DateTime variables. I do get a result but it makes no sense and provide incorrect values.
The method is supposed to return all bestsellers from yesterday (the past 24 hours). However it seems that the method returns all bestsellers that have been sold since the beginning. In the Database there's a column called "CreatedOnUtc" wich provide a date, guess this could be used as a date to test but i don't know how to access it as it's in another class.
public IList<BestsellersReportLine> DailyBestsellersReport()
{
int recordsToReturn = 5; int orderBy = 1; int groupBy = 1;
var yesterDay = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0));
var earliest = new DateTime(yesterDay.Year, yesterDay.Month, yesterDay.Day, 0, 0, 0);
var latest = earliest.Add(new TimeSpan(1, 0, 0, 0, -1));
var currentDay = DateTime.Now;
var dayBefore = DateTime.Now.AddDays(-1);
var query1 = from opv in _opvRepository.Table
where earliest <= currentDay && latest >= dayBefore
join o in _orderRepository.Table on opv.OrderId equals o.Id
join pv in _productVariantRepository.Table on opv.ProductVariantId equals pv.Id
join p in _productRepository.Table on pv.ProductId equals p.Id
select opv;
var query2 = groupBy == 1 ?
//group by product variants
from opv in query1
where earliest <= currentDay && latest >= dayBefore
group opv by opv.ProductVariantId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
:
//group by products
from opv in query1
where earliest <= currentDay && latest >= dayBefore
group opv by opv.ProductVariant.ProductId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
;
switch (orderBy)
{
case 1:
{
query2 = query2.OrderByDescending(x => x.TotalQuantity);
}
break;
case 2:
{
query2 = query2.OrderByDescending(x => x.TotalAmount);
}
break;
default:
throw new ArgumentException("Wrong orderBy parameter", "orderBy");
}
if (recordsToReturn != 0 && recordsToReturn != int.MaxValue)
query2 = query2.Take(recordsToReturn);
var result = query2.ToList().Select(x =>
{
var reportLine = new BestsellersReportLine()
{
EntityId = x.EntityId,
TotalAmount = x.TotalAmount,
TotalQuantity = x.TotalQuantity
};
return reportLine;
}).ToList();
return result;
}
There is a similar method wich has specified startTime and endTime, i believe it searches the database within a time record. Allthough this method is displayed in a view with choosable startDate/endDate in textboxes. However i need the time record for DailyBestsellersReport to be specified in the code as this will be a process running in the background.
Here is a similar period with DateTime parameteers:
public virtual IList<BestsellersReportLine> BestSellersReport(DateTime? startTime,
DateTime? endTime,int recordsToReturn = 5, int orderBy = 1, int groupBy = 1)
{
some code...
}
Any thoughts?
where earliest <= currentDay && latest >= dayBefore
It's useless condition, it's allways true.
You need test for some 'time' fields from DB, if u want to filter your db records.
Update
I suposed, that you have OrderDate column in _orderRepository.Table
than you snippet can be transformed into something like bellow:
var currentTime = DateTime.Now;
var today = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, 0, 0, 0);
var yesterDay = today.Subtract(new TimeSpan(1, 0, 0, 0));
//suppose, that we gather all data for yesterday
var query1 = from opv in _opvRepository.Table
join o in _orderRepository.Table on opv.OrderId equals o.Id
join pv in _productVariantRepository.Table on opv.ProductVariantId equals pv.Id
join p in _productRepository.Table on pv.ProductId equals p.Id
where yesterDay <= o.OrderDate && today >= o.OrderDate
select opv;
var query2 = groupBy == 1 ?
//group by product variants
from opv in query1
group opv by opv.ProductVariantId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
:
//group by products
from opv in query1
group opv by opv.ProductVariant.ProductId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
;
I have two tables (one-to-many). MeterReadings(0..1) and MeterReadingDetails(*)
I want to join these tables and group by date. Date field is in MeterReadings and Others are in MeterReadingDetails.
I used this code:
Linq
public static IEnumerable<MeterReadingsForChart> GetCustomerTotal(int CustomerId, int MeterTypeId, DateTime StartDate, DateTime EndDate, MeterReadingsTimeIntervals DateRangeType)
{
var customerReadings = from m in entity.MeterReadings
join n in entity.MeterReadingDetails on m.sno equals n.ReadingId
where m.Meters.CustomerId == CustomerId && m.ReadDate >= StartDate && m.ReadDate <= EndDate && m.Meters.TypeId == MeterTypeId
group n by new { date = new DateTime(m.ReadDate.Value.Year, m.ReadDate.Value.Month, 1) } into g
select new MeterReadingsForChart
{
ReadDate = g.Key.date,
Value = g.Sum(x => x.Value),
Name = g.FirstOrDefault().MeterReadingTypes.TypeName
};
return customerReadings;
}
MeterReadinsForChart.cs
public class MeterReadingsForChart
{
public DateTime ReadDate { get; set; }
public string Name { get; set; }
public double Value { get; set; }
}
But I got this error:
Only parameterless constructors and initializers are supported in LINQ to Entities
How can I join, group, and sum?
Try the following:
var customerReadings = (from m in entity.MeterReadings
join n in entity.MeterReadingDetails on m.sno equals n.ReadingId
where m.Meters.CustomerId == CustomerId && m.ReadDate >= StartDate && m.ReadDate <= EndDate && m.Meters.TypeId == MeterTypeId
group n by new { Year = m.ReadDate.Value.Year, Month = m.ReadDate.Value.Month} into g
select new
{
Key = g.Key,
Value = g.Sum(x => x.Value),
Name = g.FirstOrDefault().MeterReadingTypes.TypeName
}).AsEnumerable()
.Select(anon => new MeterReadingsForChart
{
ReadDate = new DateTime(anon.Key.Year, anon.Key.Month, 1),
Value = anon.Value,
Name = anon.Name
});
Unf. its ugly, but entity framework won't let you create a DateTime (being a struct it has no parameterless constructors). So in this case we want most of the result from the db and then as this streams we construct the date in memory.
I need to select date and average values from a datacontext's table and I need to group it by year and by month.
In SQL it will look like this
select Year(data) as years, Month(data) as months, Avg(value) as prices from Prices
group by Year(data),MONTH(data)
order by years, months
I've created a LINQ query
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select groupMonthAvg;
But I don't know how to get average values in result
Use the Average function.
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new
{
years = groupMonthAvg.Key.Year,
months = groupMonthAvg.Key.Month,
prices = groupMonthAvg.Average(x=>x.value)
};
Just use the Average function in your select:
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new {
groupMonthAvg.Key.Year,
groupMonthAvg.Key.Month,
YearAverage = groupMonthAvg.Average(x=>x.Year),
MonthAverage = groupMonthAvg.Average(x=>x.Month)
};
This should do it:
var query0 = from c in dc.Prices
where Convert.ToDateTime(c.data).CompareTo(left) >= 0
&& Convert.ToDateTime(c.data).CompareTo(right) <= 0
&& c.idsticker.Equals(x)
group c by new { ((DateTime)c.data).Year, ((DateTime)c.data).Month }
into groupMonthAvg
select new { Year = groupMonthAvg.Key.Year, Month = groupMonthAvg.Key.Month, Average => groupMonthAvg.Average(i => i.value) };
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();
}