Linq: количество групп строк, включая 0 для пропущенных строк
Я хотел бы обобщить список действий по количеству вхождений по дням за последнюю неделю, используя Linq to Entities. Например, допустим, у меня есть такие данные в моей базе данных:
Id | Date
1 | 2011-09-30 0:00:0
2 | 2011-10-02 0:00:00
3 | 2011-10-02 0:00:00
4 | 2011-10-02 0:00:00
5 | 2011-10-04 0:00:00
6 | 2011-10-04 1:30:30
7 | 2011-10-04 0:00:00
8 | 2011-10-06 0:00:00
и скажем, сегодняшняя дата 2011-10-07
Я хотел бы создать следующее:
Date | Count
2011-10-01 | 0
2011-10-02 | 3
2011-10-03 | 0
2011-10-04 | 3
2011-10-05 | 0
2011-10-06 | 1
2011-10-07 | 0
Вот пример техники, которую я могу использовать для группировки вхождений по дате, но мне не хватает нулей.
// Using Linq to Objects for demonstration purpose only
var activities = new List<Activity>();
activities.Add(new Activity { Id = 1, Date = new DateTime(2011, 9, 30)});
activities.Add(new Activity { Id = 2, Date = new DateTime(2011, 10, 2)});
activities.Add(new Activity { Id = 3, Date = new DateTime(2011, 10, 2)});
activities.Add(new Activity { Id = 4, Date = new DateTime(2011, 10, 2)});
activities.Add(new Activity { Id = 5, Date = new DateTime(2011, 10, 4)});
activities.Add(new Activity { Id = 6, Date = new DateTime(2011, 10, 4, 1, 30, 30) });
activities.Add(new Activity { Id = 7, Date = new DateTime(2011, 10, 4)});
activities.Add(new Activity { Id = 8, Date = new DateTime(2011, 10, 6)});
var data = (from a in activities
group a by a.Date.Date into g
where g.Key > DateTime.UtcNow.AddDays(-7)
select new {
Date = g.Key,
Count = g.Count()
}).ToList();
Вот результат:
Date | Count
2011-10-02 | 3
2011-10-04 | 3
2011-10-06 | 1
Кто-нибудь знает, как я могу включить недостающие нули, используя Linq to Entities? Я всегда могу перечислить результаты, как только они будут в памяти, но было бы неплохо получить их непосредственно из базы данных.
3 ответа
Это может быть так же просто сгенерировать с помощью цикла, так как данные еще не доступны в базе данных, и сделать это через LINQ будет сложно:
var firstDate = data.Min(d => d.Date).AddDays(-1);
var lastDate = data.Max(d => d.Date).AddDays(1);
for (var date = firstDate; date <= lastDate; date = date.AddDays(1))
if (!data.Exists(d => d.Date == date))
data.Add(new { Date = date, Count = 0 });
data = data.OrderBy(d => d.Date);
// do something with data
Если ваш набор данных не очень, очень большой, этот запрос в памяти и корректировка списка будут арахисами по сравнению со временем, которое требуется для запроса базы данных.
Вы можете сгенерировать массив дат, учитывая начальное значение и зная количество дней, которые вы хотите вернуть. Здесь я жестко его кодирую, но вы можете вытащить его из списка активности, используя минимальное и максимальное значения и определив соответственно начальное значение и количество дней.
var allDays = Enumerable.Range(0,7).Select (i => new DateTime(2011,9,30).AddDays(i) );
Как только вы это сделаете, вы можете использовать подвыбор или объединение для объединения:
var data = (from d in allDays
select new {
Date = d,
Count = activities.Count (a => a.Date.Date == d)
}).ToList();
Если вы хотите получить результат в одном запросе linq, вам придется выполнить эту операцию в базе данных. Таким образом, вы должны иметь все дни в вашей базе данных, даже если они имеют 0 активности. Это означает, что вы должны создать новую таблицу.
В вашем сценарии я бы создал таблицу календаря и внешнее объединение для этой таблицы, чтобы получить дни с 0 действиями.