1. Introduction to Aggregate Analysis

Aggregation operation is a very important concept in database queries. It is usually used for statistical analysis such as summation, counting, averaging, maximum, and minimum values. These operations help users extract meaningful information from a large amount of data, providing support for data analysis and decision-making. The functions implemented in the database for aggregation are usually referred to as aggregate functions.

2. Basic Aggregate Operations

2.1 Concept of Aggregate Functions

Aggregate functions are functions used in database query languages to perform a series of calculations and return a single value. In SQL and similar query languages, aggregate functions can operate on a column of data and return a single value, such as sum (SUM), average (AVG), count (COUNT), maximum (MAX), and minimum (MIN). When we need to perform data statistical analysis, aggregate functions are important tools for processing data sets and extracting statistical data.

2.2 Single-Field Aggregation

In practical applications, single-field aggregation analysis is a very common requirement, often used to obtain statistical results such as the sum, average, maximum, and minimum values of a particular column. Suppose we have a payment information table, and we want to calculate the total amount paid by users. Using the ent framework, we can construct a query from the entity and apply aggregate functions as follows:

func Do(ctx context.Context, client *ent.Client) {
    // Calculate the sum of the Amount field for the Payment entity
    sum, err := client.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Failed to get the sum: %v", err)
    }
    log.Printf("Total amount of payments: %d", sum)
}

In the above code snippet, we initiate a query for the payment entity using client.Payment.Query(), then use the Aggregate() method to call the ent.Sum function with payment.Amount as the parameter to calculate the total amount of payments. We use .Int(ctx) to convert the aggregate result to an integer and log it.

2.3 Multi-Field Aggregation

In many cases, we need to perform aggregation operations on multiple fields rather than just one. In this section, we will demonstrate how to achieve multi-field aggregation through an example.

In this example, we will sum, find the minimum, find the maximum, and count the Age field in the pet table.

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), // Sum of Age
            ent.Min(pet.FieldAge), // Minimum Age
            ent.Max(pet.FieldAge), // Maximum Age
            ent.Count(),           // Count
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Query failed: %v", err)
    }
    // Output all the aggregate results
    for _, agg := range v {
        fmt.Printf("Sum: %d Min: %d Max: %d Count: %d\n", agg.Sum, agg.Min, agg.Max, agg.Count)
    }
}

In the above code, we use the Aggregate function to perform multi-field aggregation, and use the Scan function to store the aggregation results in the slice v. Then, we iterate through v to output all the aggregate results.

3. Application of Group By Aggregation

3.1. Using Group By to Group Fields

In database operations, Group By is a common method for grouping data. In this section, we will learn how to use Group By to group data in the database.

Tutorial example, how to group one or more fields using Group By.

Assuming we have a user table and we need to group the name field of the users and calculate the number of users in each group. Below is a code example of how to achieve this requirement:

func Do(ctx context.Context, client *ent.Client) {
    names, err := client.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Failed to execute grouped query: %v", err)
    }
    // Output the name of each group
    for _, name := range names {
        fmt.Println("Group name:", name)
    }
}

In the above code, we use the query builder's GroupBy method to specify which field we want to group by.

3.2. Grouping and Aggregating Multiple Fields

Sometimes, we want to group data based on multiple fields and perform aggregate functions on each group. Below is an example of how to achieve this requirement:

The following code demonstrates how to group data in the user table based on the name and age fields, and calculate the total age and the number of users in each group.

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("Failed to execute multiple fields grouping and aggregation query: %v", err)
    }
    // Output detailed information for each group
    for _, group := range v {
        fmt.Printf("Name: %s Age: %d Sum: %d Count: %d\n", group.Name, group.Age, group.Sum, group.Count)
    }
}

In this example, we not only group the data based on the name and age fields in the user table, but also use the Count and Sum aggregate functions to calculate the total number of records and the total age in each group.

4. Combining Having with Group By

The Having clause filters the aggregate results obtained after the Group By operation. The following example shows how to select only the users with the maximum age in each role:

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("Failed to execute Having combined with Group By query: %v", err)
    }
    // Output user information that satisfies the Having condition
    for _, user := range users {
        fmt.Printf("ID: %d Age: %d Role: %s\n", user.Id, user.Age, user.Role)
    }
}

The above code will generate an equivalent SQL query to select the users with the maximum age in each role.