[英]Filling in missing dates using a linq group by date query
我有一個Linq查詢,該查詢基本上計算在特定日期創建了多少個條目,這是通過按年,月,日分組來完成的 。 問題是,因為有些日子沒有任何輸入,所以我需要用0計數回填那些缺少的“日歷日”。 我的猜測是,這可能可以用Union或其他方式完成,甚至可以使用一些簡單的for循環在查詢后處理記錄。
這是查詢:
from l in context.LoginToken
where l.CreatedOn >= start && l.CreatedOn <= finish
group l by
new{l.CreatedOn.Year, l.CreatedOn.Month, l.CreatedOn.Day} into groups
orderby groups.Key.Year , groups.Key.Month , groups.Key.Day
select new StatsDateWithCount {
Count = groups.Count(),
Year = groups.Key.Year,
Month = groups.Key.Month,
Day = groups.Key.Day
}));
如果我有2009年12月12日至12月4日的數據(簡化):
12/1/2009 20
12/2/2009 15
12/4/2009 16
我想要一個通過代碼添加0/12/3/2009的條目。
我知道通常應該在數據庫中使用非規范化表來完成此操作,該表可以用數據填充或聯接到日歷表,但是我的問題是我該如何在代碼中完成此操作?
可以在Linq中完成嗎? 應該在Linq中完成嗎?
我今天剛做。 我從數據庫中收集了完整的數據,然后生成了“樣品空”表。 最后,我對空表與真實數據進行了外部連接,並使用DefaultIfEmpty()構造來處理何時從數據庫中丟失行以將其填充為默認值。
這是我的代碼:
int days = 30;
// Gather the data we have in the database, which will be incomplete for the graph (i.e. missing dates/subsystems).
var dataQuery =
from tr in SourceDataTable
where (DateTime.UtcNow - tr.CreatedTime).Days < 30
group tr by new { tr.CreatedTime.Date, tr.Subsystem } into g
orderby g.Key.Date ascending, g.Key.SubSystem ascending
select new MyResults()
{
Date = g.Key.Date,
SubSystem = g.Key.SubSystem,
Count = g.Count()
};
// Generate the list of subsystems we want.
var subsystems = new[] { SubSystem.Foo, SubSystem.Bar }.AsQueryable();
// Generate the list of Dates we want.
var datetimes = new List<DateTime>();
for (int i = 0; i < days; i++)
{
datetimes.Add(DateTime.UtcNow.AddDays(-i).Date);
}
// Generate the empty table, which is the shape of the output we want but without counts.
var emptyTableQuery =
from dt in datetimes
from subsys in subsystems
select new MyResults()
{
Date = dt.Date,
SubSystem = subsys,
Count = 0
};
// Perform an outer join of the empty table with the real data and use the magic DefaultIfEmpty
// to handle the "there's no data from the database case".
var finalQuery =
from e in emptyTableQuery
join realData in dataQuery on
new { e.Date, e.SubSystem } equals
new { realData.Date, realData.SubSystem } into g
from realDataJoin in g.DefaultIfEmpty()
select new MyResults()
{
Date = e.Date,
SubSystem = e.SubSystem,
Count = realDataJoin == null ? 0 : realDataJoin.Count
};
return finalQuery.OrderBy(x => x.Date).AsEnumerable();
本質上,我最終在這里所做的是創建一個相同類型的列表,其中所有日期都在范圍內,計數的值為0。 然后將我原始查詢的結果與此列表進行合並。 主要障礙只是創建自定義IEqualityComparer。 有關更多詳細信息, 請單擊此處:
您可以生成從“開始”開始到“完成”結束的日期列表,然后逐步檢查每個日期的計數數量
我制作了一個輔助函數,該函數旨在與匿名類型一起使用,並以盡可能通用的方式重用。
假設這是您要獲取每個日期的訂單列表的查詢。
var orders = db.Orders
.GroupBy(o => o.OrderDate)
.Select(o => new
{
OrderDate = o.Key,
OrderCount = o.Count(),
Sales = o.Sum(i => i.SubTotal)
}
.OrderBy(o => o.OrderDate);
為了使我的功能正常工作,請注意,此列表必須按日期排序。 如果我們有一天沒有銷售,那清單上會有一個空洞。
現在,該函數將使用默認值(匿名類型的實例)填補空白。
private static IEnumerable<T> FillInEmptyDates<T>(IEnumerable<DateTime> allDates, IEnumerable<T> sourceData, Func<T, DateTime> dateSelector, Func<DateTime, T> defaultItemFactory)
{
// iterate through the source collection
var iterator = sourceData.GetEnumerator();
iterator.MoveNext();
// for each date in the desired list
foreach (var desiredDate in allDates)
{
// check if the current item exists and is the 'desired' date
if (iterator.Current != null &&
dateSelector(iterator.Current) == desiredDate)
{
// if so then return it and move to the next item
yield return iterator.Current;
iterator.MoveNext();
// if source data is now exhausted then continue
if (iterator.Current == null)
{
continue;
}
// ensure next item is not a duplicate
if (dateSelector(iterator.Current) == desiredDate)
{
throw new Exception("More than one item found in source collection with date " + desiredDate);
}
}
else
{
// if the current 'desired' item doesn't exist then
// create a dummy item using the provided factory
yield return defaultItemFactory(desiredDate);
}
}
}
用法如下:
// first you must determine your desired list of dates which must be in order
// determine this however you want
var desiredDates = ....;
// fill in any holes
var ordersByDate = FillInEmptyDates(desiredDates,
// Source list (with holes)
orders,
// How do we get a date from an order
(order) => order.OrderDate,
// How do we create an 'empty' item
(date) => new
{
OrderDate = date,
OrderCount = 0,
Sales = 0
});
desiredDates
和sourceData
必須按順序排列 sourceData
但在沒有這樣的檢查desiredDates
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.