簡體   English   中英

使用LINQ按日期分組查詢來填寫缺少的日期

[英]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
                               });
  • 必須確保所需日期列表中沒有重復項
  • desiredDatessourceData必須按順序排列
  • 因為如果您使用的是匿名類型,該方法是通用的,則編譯器會自動告訴您“默認”項目與常規項目的“形狀”是否不同。
  • 現在,我包括在重復項檢查sourceData但在沒有這樣的檢查desiredDates
  • 如果要確保按日期排序列表,則需要添加其他代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM