В настоящее время у меня есть таблица в SQL людей с датой начала и окончания их проживания. Я хотел бы иметь возможность найти количество людей, живущих в этом месте за каждый год в списке лет.

Т.е.

Person, Start, End

A, 2013, 2014

B, 2013, 2015

C, 2014, 2015

Должен дать мне что-то вроде:

2012, 0


2013, 2


2014, 3

2015, 2

Моя проблема в том, что у меня нет списка лет, хранящихся где-либо в SQL. Мне нужно было бы каким-то образом сгенерировать этот список лет в SQL... У меня есть этот список лет в памяти, но я не могу выполнять соединения с данными в памяти, не загружая всю свою таблицу в память... Я не могу понять способ получить мой список лет на стороне сервера, чтобы я мог выполнить этот запрос... Однако, как только он появится, я почти уверен, что все, что мне нужно сделать, это какое-то левое внешнее соединение, а затем сгруппировать по годам и выберите сумму... но я не могу туда добраться, пока не найду способ сгенерировать все эти годы.

0
Coder 101 3 Июл 2015 в 18:52
В зависимости от используемой вами версии sql-сервера вы можете просмотреть APPLY и CROSS APPLY для создания этих строк для каждого года. Вам также может понадобиться таблица лет, чтобы присоединиться. В этой таблице будут минимальные и максимальные годы вашего набора данных. Если данные не слишком велики, можно обойтись написанием простых функций.
 – 
Emacs User
3 Июл 2015 в 19:46

2 ответа

Я предполагаю, что из вашего тега Linq вы также можете использовать некоторые объекты Linq To Objects. В этом случае вы можете использовать Linq-To-Sql, чтобы получить сводку людей, например

var grouped = (from r in Person
    group r by new { r.Start, r.End } into results
    select new { results.Key.Start, results.Key.End, count = results.Count()}
    ).ToList();

Затем переключитесь на объекты Linq To, чтобы найти первый и последний год.

var firstYear = grouped.Min(a=>a.Start) ?? 0;
var lastYear  = grouped.Max(a=>a.End) ?? 0;

А затем перебрать все годы, чтобы найти общее количество людей, например, что-то вроде

List<object> yearlySummary = new List<object>();

for (int year = firstYear; year <= lastYear; year++)
{
     var no = (from r in grouped where r.Start <= year && r.End >= year select (int?) r.count).Sum() ?? 0;

     yearlySummary.Add(new { year, no} );   
}
0
sgmoore 3 Июл 2015 в 20:00

Вы не сможете сделать это в одном запросе, если у вас нет другой таблицы, содержащей годы, которые вы хотите отобразить.


Простое решение (2 запроса + 1 в год)

Один из вариантов — получить минимальные и максимальные даты, используя по 1 запросу, а затем просмотреть даты, чтобы получить информацию, используя 1 запрос в год.

using (var db = new Context())
{
    var combinedQuery = db.Dates.Select(d => d.start).Union(db.Dates.Select(d => d.end));
    int min = combinedQuery.Min();
    int max = combinedQuery.Max();

    for (int year = min - 1; year <= max; year++)
    {
        var count = db.Dates.Count(x => year >= x.start && year <= x.end);
        Console.WriteLine("{0}, {1}", year, count);
    }

    Console.ReadLine();
}

Не так просто (2 запроса)

В качестве альтернативы вы можете получить минимальную и максимальную дату, используя один запрос, воспользовавшись двумя подзапросами.

Теперь, когда у вас есть минимальная и максимальная даты, вы можете просмотреть их, чтобы сгенерировать запрос года (это будет действовать как ваша псевдо-таблица года, которой у вас нет). Это делается путем объединения каждого года вместе, а затем с помощью подвыборки для получения подсчета.

using (var db = new Context())
{    
    var combinedQuery = db.Dates.Select(d => d.start).Union(db.Dates.Select(d => d.end));
    var minAndMax = db.tests.Select(x => new 
                    { 
                       Min = combinedQuery.Min(), 
                       Max = combinedQuery.Max() 
                    }).FirstOrDefault();
    int min = minAndMax.Min;
    int max = minAndMax.Max;

    IQueryable<int> yearQuery = null;

    for (int year = min - 1; year <= max; year++)
    {
        int tempYear = year;
        var query = db.tests.Take(1).Select(x => tempYear);

        if (yearQuery == null)
        {
            yearQuery = query;
        }
        else
        {
            yearQuery = yearQuery.Concat(query);
        }
    }

    var results = (from year in yearQuery
                   select new
                   {
                       Year = year,
                       Count = db.Dates.Count(x => year >= x.start && year <= x.end),
                   }).ToList();

    Console.ReadLine();
}
  • Обратите внимание, что linq-to-entities и linq-to-sql действуют немного по-разному, сгенерированные запросы использовали linq-to-entities.
0
Aducci 3 Июл 2015 в 21:01