Эффективный запрос только для первых N строк для каждого уникального идентификатора

Это продолжение этого вопроса.

TLDR:

Вопрос:

Я хочу отфильтровать запрос, чтобы сохранить только первые n строк для каждого уникального идентификатора.

Ответ:

query = query.GroupBy(q => q.ID).SelectMany(g => g.Take(n)); 

Проблема с этим ответом заключается в том, что для 80 000 + строк оценка запроса занимает гораздо больше времени, чем фильтрация по итерации ( foreach ) (по крайней мере, в два раза медленнее). Рассматривая SQL, сгенерированный этим ответом, используется CROSS APPLY , скорее всего, для SelectMany() .

Эта ссылка описывает, что делает CROSS APPLY :

Оператор APPLY позволяет вам присоединиться к двум табличным выражениям; правильное выражение таблицы обрабатывается каждый раз для каждой строки из левого табличного выражения.

Короче говоря, я ищу фильтр-запрос, который эффективно собирает верхние N строк для каждого уникального ID .

Решение Linq с объясненным SQL было бы идеальным.

Я нашел свой ответ в SQL здесь (решение SQL 2000 внизу) и удалось реализовать версию Queryable / Linq:

 query = tableQueryable.Where(a => tableQueryable.Where(b => b.ID == a.ID) .OrderByDescending(o => o.Timestamp) .Take(N) .Select(s => s.PK) .Contains(a.PK) ).OrderByDescending(d => d.Timestamp); 

Довольно стандартный шаблон «подзапроса». Это намного быстрее на большом столе.

L2S не имеет номера строки, поэтому трюк Мартина не может быть использован. Я тоже столкнулся с этой проблемой, и, насколько мне известно, это оптимальное решение L2S (которое не использует собственный SQL).

Вы можете попытаться снести все результаты в приложение и сделать там номер строки. Это может повредить или принести пользу производительности. Какое это зависит от конкретного случая.