1. Введение в агрегатный анализ

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

2. Основные операции агрегации

2.1. Понятие агрегатных функций

Агрегатные функции - это функции, используемые в языках запросов к базе данных для выполнения серии вычислений и возврата одного значения. В SQL и аналогичных языках запросов агрегатные функции могут работать с колонкой данных и возвращать одно значение, такое как сумма (SUM), среднее (AVG), количество (COUNT), максимальное (MAX) и минимальное (MIN). Когда нам нужно провести статистический анализ данных, агрегатные функции являются важным инструментом для обработки наборов данных и извлечения статистических данных.

2.2. Агрегация по одному полю

В прикладных приложениях анализ с одним полем агрегации является очень распространенной задачей, часто используемой для получения статистических результатов, таких как сумма, среднее, максимальное и минимальное значения определенной колонки. Предположим, у нас есть таблица с информацией о платежах, и мы хотим рассчитать общую сумму, оплаченную пользователями. Используя фреймворк ent, мы можем составить запрос из сущности и применить агрегатные функции следующим образом:

func Do(ctx context.Context, client *ent.Client) {
    // Рассчитываем сумму поля Amount для сущности Payment
    sum, err := client.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Ошибка при получении суммы: %v", err)
    }
    log.Printf("Общая сумма платежей: %d", sum)
}

В приведенном выше фрагменте кода мы инициируем запрос для сущности платежей, используя client.Payment.Query(), затем используем метод Aggregate() для вызова функции ent.Sum с payment.Amount в качестве параметра для расчета общей суммы платежей. Мы используем .Int(ctx) для преобразования результата агрегации в целое число и выводим его в журнал.

2.3. Агрегация по нескольким полям

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

В данном примере мы суммируем, находим минимум, находим максимум и подсчитываем поле Age в таблице pets.

func Do(ctx context.Context, client *ent.Client) {
    var v []struct {
        Sum, Min, Max, Count int
    }
    err := client.Pet.Query().
        Aggregate(
            ent.Sum(pet.FieldAge), // Сумма Age
            ent.Min(pet.FieldAge), // Минимальный возраст
            ent.Max(pet.FieldAge), // Максимальный возраст
            ent.Count(),           // Количество
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Ошибка запроса: %v", err)
    }
    // Выводим все результаты агрегации
    for _, agg := range v {
        fmt.Printf("Сумма: %d Минимум: %d Максимум: %d Количество: %d\n", agg.Sum, agg.Min, agg.Max, agg.Count)
    }
}

В приведенном выше коде мы используем функцию Aggregate для выполнения агрегации по нескольким полям и используем функцию Scan для сохранения результатов агрегации в срезе v. Затем мы перебираем v, чтобы вывести все результаты агрегации.

3. Применение агрегации с использованием Group By

3.1. Использование Group By для группировки полей

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

Пример руководства, как группировать одно или несколько полей с использованием Group By.

Предположим, у нас есть таблица пользователей, и нам нужно сгруппировать поле имя пользователей и подсчитать количество пользователей в каждой группе. Ниже приведен пример кода, как достичь этого требования:

func Do(ctx context.Context, client *ent.Client) {
    names, err := client.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Ошибка выполнения группированного запроса: %v", err)
    }
    // Выводим имя каждой группы
    for _, name := range names {
        fmt.Println("Имя группы:", name)
    }
}

В приведенном выше коде мы используем метод GroupBy построителя запросов для указания поля, по которому мы хотим сгруппироваться.

3.2. Группировка и агрегирование по нескольким полям

Иногда нам нужно сгруппировать данные на основе нескольких полей и выполнить агрегирующие функции для каждой группы. Ниже приведен пример того, как выполнить этот запрос:

Нижеприведенный код демонстрирует, как сгруппировать данные в таблице пользователей по полям name и age, и рассчитать общий возраст и количество пользователей в каждой группе.

func Do(ctx context.Context, client *ent.Client) {
    var v []struct {
        Name  string `json:"name"`
        Age   int    `json:"age"`
        Sum   int    `json:"sum"`
        Count int    `json:"count"`
    }
    err := client.User.Query().
        GroupBy(user.FieldName, user.FieldAge).
        Aggregate(ent.Count(), ent.Sum(user.FieldAge)).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Ошибка выполнения запроса группировки данных по нескольким полям и их агрегации: %v", err)
    }
    // Вывод подробной информации о каждой группе
    for _, group := range v {
        fmt.Printf("Имя: %s Возраст: %d Общий возраст: %d Количество: %d\n", group.Name, group.Age, group.Sum, group.Count)
    }
}

В этом примере мы не только группируем данные по полям name и age в таблице пользователей, но и используем агрегирующие функции Count и Sum для вычисления общего количества записей и общего возраста в каждой группе.

4. Совмещение Having с Group By

Оператор Having фильтрует агрегированные результаты, полученные после операции Group By. В следующем примере показано, как выбрать только пользователей с максимальным возрастом в каждой роли:

func Do(ctx context.Context, client *ent.Client) {
    var users []struct {
        Id      Int
        Age     Int
        Role    string
    }
    err := client.User.Query().
        Modify(func(s *sql.Selector) {
            s.GroupBy(user.FieldRole)
            s.Having(
                sql.EQ(
                    user.FieldAge,
                    sql.Raw(sql.Max(user.FieldAge)),
                ),
            )
        }).
        ScanX(ctx, &users)
    if err != nil {
        log.Fatalf("Ошибка выполнения запроса совмещения Having с Group By: %v", err)
    }
    // Вывод информации о пользователях, удовлетворяющих условию Having
    for _, user := range users {
        fmt.Printf("ID: %d Возраст: %d Роль: %s\n", user.Id, user.Age, user.Role)
    }
}

В вышеприведенном примере будет сформирован эквивалентный SQL-запрос для выбора пользователей с максимальным возрастом в каждой роли.