2009-09-23 54 views
6

Tôi có truy vấn LINQ về cơ bản đếm số lượng mục được tạo vào một ngày cụ thể, được thực hiện bằng cách nhóm theo năm, tháng, ngày. Vấn đề là bởi vì một số ngày sẽ không có bất kỳ mục nào tôi cần phải điền vào những "ngày theo lịch" bị thiếu với mục nhập là 0. Tôi đoán rằng điều này có thể có thể được thực hiện với một Liên minh hoặc một cái gì đó, hoặc thậm chí một số đơn giản cho vòng lặp để xử lý các bản ghi sau khi truy vấn.Điền ngày thiếu bằng cách sử dụng nhóm LINQ theo truy vấn ngày

Đây là truy vấn:

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 
                    })); 

Nếu tôi có dữ liệu cho 12/1 - 2009/12/04 tương tự (giản thể):

12/1/2009 20 
12/2/2009 15 
12/4/2009 16 

Tôi muốn một mục với 12/3/2009 0 được thêm bằng mã.

Tôi biết rằng nói chung điều này nên được thực hiện trong DB bằng cách sử dụng bảng không chuẩn hóa mà bạn cư trú với dữ liệu hoặc tham gia vào bảng lịch, nhưng câu hỏi của tôi là làm cách nào để thực hiện điều này trong mã?
Nó có thể được thực hiện trong LINQ không? Nó nên được thực hiện trong LINQ?

+0

có thể trùng lặp của [LINQ nhóm theo ngày - bao gồm các ngày trống KHÔNG sử dụng tham gia] (http://stackoverflow.com/questions/17086120/linq-group-by-date-include-empty-days-without-using-join) – ensisNoctis

Trả lời

0

Tôi vừa làm điều này hôm nay. Tôi thu thập dữ liệu hoàn chỉnh từ cơ sở dữ liệu và sau đó tạo ra một bảng "mẫu trống". Cuối cùng, tôi đã thực hiện một kết nối bên ngoài của bảng trống với dữ liệu thực và đã sử dụng cấu trúc DefaultIfEmpty() để xử lý việc biết khi nào một hàng bị thiếu trong cơ sở dữ liệu để điền nó với các giá trị mặc định.

Dưới đây là mã của tôi:

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(); 
+1

Điều này rất giống với những gì tôi đã kết thúc nhưng đã làm một Liên minh về kết quả thay vì thực hiện một kết nối. –

0

Bạn có thể tạo danh sách ngày bắt đầu từ "bắt đầu" và kết thúc tại "kết thúc", một sau đó từng bước kiểm tra số đếm cho từng ngày riêng

+0

Đây là ok, nhưng tôi muốn xem làm thế nào nó có thể được thực hiện bằng cách sử dụng một số cấu trúc LINQ như nhà điều hành Union. –

1

Về cơ bản những gì tôi đã kết thúc làm gì ở đây là tạo danh sách cùng loại với tất cả các ngày trong phạm vi và 0 giá trị cho số. Sau đó, kết hợp các kết quả từ truy vấn ban đầu của tôi với danh sách này. Trở ngại chính là tạo một IEqualityComparer tùy chỉnh. Để biết thêm chi tiết tại đây: click here

0

tôi đã thực hiện một chức năng helper được thiết kế để được sử dụng với các loại vô danh, và tái sử dụng theo cách như quát càng tốt.

Giả sử đây là truy vấn của bạn để nhận danh sách đơn đặt hàng cho mỗi ngày.

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); 

Để chức năng của tôi hoạt động, xin lưu ý danh sách này phải được sắp xếp theo ngày. Nếu chúng tôi có một ngày không bán hàng thì sẽ có một lỗ hổng trong danh sách.

Bây giờ cho hàm sẽ điền vào chỗ trống với giá trị mặc định (ví dụ của loại ẩn danh).

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); 
      } 
     } 
    } 

Việc sử dụng như sau:

// 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 
           }); 
  • Phải chắc chắn rằng không có bản sao trong danh sách ngày mong muốn
  • Cả desiredDatessourceData phải theo thứ tự
  • Vì phương pháp là chung chung nếu bạn đang sử dụng một loại vô danh sau đó trình biên dịch sẽ tự động cho bạn biết nếu mục 'mặc định' của bạn không giống 'hình dạng' như một mục thông thường.
  • Ngay bây giờ tôi bao gồm một tấm séc cho các hạng mục trùng lặp trong sourceData nhưng không có tờ séc đó trong desiredDates
  • Nếu bạn muốn đảm bảo các danh sách được sắp xếp theo ngày, bạn sẽ cần phải thêm mã thêm
+0

Tôi nghĩ đây là một kịch bản 'kinh doanh' cụ thể mà tôi nghĩ rằng cố gắng ép nó vào một cấu trúc LINQ 'tao nhã' là phản tác dụng - nhưng đây là điều thứ hai tao nhã nhất tôi có thể nghĩ ra –

Các vấn đề liên quan