Есть ли у вас опыт написания агрегирующих запросов в SQL и LINQ?

«Есть ли у вас опыт написания агрегирующих запросов в SQL и LINQ?» — вопрос из категории Базы данных, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, есть значительный опыт. Агрегация данных — это базовая операция для аналитики и отчетов.

1. Агрегация в LINQ to Entities (EF Core): Используется для работы с данными на стороне приложения или формирования SQL-запросов через EF.

// Пример: Анализ продаж по категориям за последний месяц
var salesReport = await dbContext.Orders
    .Where(o => o.OrderDate >= DateTime.UtcNow.AddMonths(-1))
    .GroupBy(o => o.Product.Category.Name) // Группировка по категории
    .Select(g => new SalesSummaryDto // Проекция в DTO
    {
        CategoryName = g.Key,
        TotalRevenue = g.Sum(o => o.Quantity * o.UnitPrice),
        AverageOrderValue = g.Average(o => o.Quantity * o.UnitPrice),
        NumberOfOrders = g.Count(),
        MostSoldProduct = g
            .GroupBy(o => o.Product.Name)
            .OrderByDescending(pg => pg.Sum(o => o.Quantity))
            .FirstOrDefault().Key
    })
    .OrderByDescending(r => r.TotalRevenue)
    .ToListAsync();

2. Агрегация в "сыром" SQL: Для сложных отчетов или оптимизации часто пишу прямые SQL-запросы.

-- Аналогичный отчет, но с использованием оконных функций
SELECT
    p.CategoryName,
    SUM(o.Quantity * o.UnitPrice) AS TotalRevenue,
    AVG(o.Quantity * o.UnitPrice) AS AverageOrderValue,
    COUNT(DISTINCT o.Id) AS NumberOfOrders,
    FIRST_VALUE(p.ProductName) OVER (
        PARTITION BY p.CategoryName 
        ORDER BY SUM(o.Quantity) OVER (PARTITION BY p.ProductId) DESC
    ) AS MostSoldProduct
FROM Orders o
JOIN Products p ON o.ProductId = p.Id
WHERE o.OrderDate >= DATEADD(month, -1, GETUTCDATE())
GROUP BY p.CategoryName, p.ProductId, p.ProductName
ORDER BY TotalRevenue DESC;

Основные агрегатные функции и их применение:

  • Count(), Sum(), Average(), Min(), Max() — базовые агрегаты.
  • GroupBy() — фундамент для агрегации, группирует элементы по ключу.
  • Оконные функции (SQL, EF Core 8.0+): ROW_NUMBER(), RANK(), SUM(...) OVER (PARTITION BY ...) для вычислений внутри группы без свертки строк. Это мощный инструмент для расчетов типа "доля в категории", "кумулятивная сумма", "ранжирование".
  • Условия в агрегатах: Использование WHERE перед GROUP BY фильтрует строки, а HAVING — фильтрует уже сгруппированные результаты.