1. Wprowadzenie do Analizy Agregatów

Operacja agregacji to bardzo ważne pojęcie w zapytaniach do bazy danych. Zazwyczaj używana jest do analizy statystycznej, taka jak sumowanie, zliczanie, obliczanie średniej, znajdowanie wartości maksymalnej i minimalnej. Te operacje pomagają użytkownikom wyciągać znaczące informacje z dużej ilości danych, zapewniając wsparcie dla analizy danych i podejmowania decyzji. Funkcje realizujące agregację w bazie danych zwykle nazywane są funkcjami agregatowymi.

2. Podstawowe Operacje Agregacji

2.1 Pojęcie Funkcji Agregatowych

Funkcje agregatowe są funkcjami używanymi w językach zapytań do bazy danych do przeprowadzenia serii obliczeń i zwrócenia pojedynczej wartości. W języku SQL i podobnych językach zapytań, funkcje agregatowe mogą działać na kolumnie danych i zwrócić pojedynczą wartość, taką jak suma (SUM), średnia (AVG), zliczanie (COUNT), maksymalna (MAX) i minimalna (MIN). Gdy potrzebujemy przeprowadzić analizę statystyczną danych, funkcje agregatowe są ważnym narzędziem do przetwarzania zestawów danych i wyodrębniania danych statystycznych.

2.2 Agregacja Pojedynczego Pola

W praktyce, analiza agregacji jednego pola jest bardzo częstym wymaganiem, często używanym do uzyskania wyników statystycznych, takich jak suma, średnia, maksimum i minimum danej kolumny. Załóżmy, że mamy tabelę informacji o płatnościach i chcemy obliczyć całkowitą kwotę zapłaconą przez użytkowników. Korzystając z frameworka ent, możemy skonstruować zapytanie z encji i zastosować funkcje agregatowe w sposób następujący:

func Do(ctx context.Context, client *ent.Client) {
    // Oblicz sumę pola Amount dla encji Payment
    suma, err := client.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Nie udało się uzyskać sumy: %v", err)
    }
    log.Printf("Całkowita kwota płatności: %d", suma)
}

W powyższym fragmencie kodu inicjujemy zapytanie dla encji płatności używając client.Payment.Query(), następnie używamy metody Aggregate() do wywołania funkcji ent.Sum z payment.Amount jako parametrem do obliczenia całkowitej kwoty płatności. Używamy .Int(ctx) do konwersji wyniku agregacji na liczbę całkowitą i zalogowania tego.

2.3 Agregacja Wielu Pól

W wielu przypadkach potrzebujemy przeprowadzić operacje agregacji na wielu polach, a nie tylko na jednym. W tej sekcji, pokażemy, jak osiągnąć agregację wielu pól na przykładzie.

W tym przykładzie, będziemy sumować, znaleźć minimum, maksimum i policzyć pole Age w tabeli pet.

func Do(ctx context.Context, client *ent.Client) {
    var v []struct {
        Suma, Min, Max, Count int
    }
    err := client.Pet.Query().
        Aggregate(
            ent.Sum(pet.FieldAge), // Suma wieku
            ent.Min(pet.FieldAge), // Minimum wiek
            ent.Max(pet.FieldAge), // Maksimum wiek
            ent.Count(),           // Licznik
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Zapytanie nie powiodło się: %v", err)
    }
    // Wypisanie wszystkich wyników agregacji
    for _, agg := range v {
        fmt.Printf("Suma: %d Min: %d Max: %d Liczba: %d\n", agg.Suma, agg.Min, agg.Max, agg.Count)
    }
}

W powyższym kodzie, używamy funkcji Aggregate do przeprowadzenia operacji agregacji wielu pól i używamy funkcji Scan do przechowywania wyników agregacji w slicu v. Następnie, iterujemy przez v, aby wypisać wszystkie wyniki agregacji.

3. Zastosowanie Agregacji Group By

3.1. Korzystanie z Group By do Grupowania Pól

W operacjach bazodanowych, Group By jest powszechną metodą grupowania danych. W tej sekcji, dowiemy się, jak korzystać z Group By do grupowania danych w bazie danych.

Przykładowy samouczek, jak grupować jedno lub więcej pól za pomocą Group By.

Załóżmy, że mamy tabelę użytkowników i potrzebujemy zgrupować pole name użytkowników oraz obliczyć liczbę użytkowników w każdej grupie. Poniżej jest przykładowy kod, jak osiągnąć to wymaganie:

func Do(ctx context.Context, client *ent.Client) {
    nazwy, err := client.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Nie udało się wykonać zapytania grupującego: %v", err)
    }
    // Wypisz nazwę każdej grupy
    for _, name := range nazwy {
        fmt.Println("Nazwa grupy:", name)
    }
}

W powyższym kodzie, używamy metody GroupBy kreatora zapytań do określenia, według którego pola chcemy grupować.

3.2. Grupowanie i agregacja wielu pól

Czasem chcemy pogrupować dane na podstawie wielu pól i wykonać funkcje agregujące w każdej grupie. Poniżej znajduje się przykład, jak to osiągnąć:

Poniższy kod przedstawia, jak pogrupować dane w tabeli użytkowników na podstawie pól name i age, oraz obliczyć sumę wieku i liczbę użytkowników w każdej grupie.

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("Nie udało się wykonać zapytania grupowania i agregacji wielu pól: %v", err)
    }
    // Wyświetlenie szczegółowych informacji dla każdej grupy
    for _, group := range v {
        fmt.Printf("Name: %s Age: %d Sum: %d Count: %d\n", group.Name, group.Age, group.Sum, group.Count)
    }
}

W tym przykładzie grupujemy dane nie tylko na podstawie pól name i age w tabeli użytkowników, ale także używamy funkcji agregujących Count i Sum do obliczenia całkowitej liczby rekordów i całkowitego wieku w każdej grupie.

4. Łączenie Having z Group By

Klauzula Having filtrowe wyniki agregacji uzyskane po operacji Group By. Poniższy przykład pokazuje, jak wybrać tylko użytkowników z maksymalnym wiekiem w każdej roli:

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("Nie udało się wykonać zapytania Having połączonego z Group By: %v", err)
    }
    // Wyświetlenie informacji o użytkowniku, który spełnia warunek Having
    for _, user := range users {
        fmt.Printf("ID: %d Age: %d Role: %s\n", user.Id, user.Age, user.Role)
    }
}

Powyższy kod wygeneruje równoważne zapytanie SQL do wybrania użytkowników z maksymalnym wiekiem w każdej roli.