1. Introducción al Análisis de Agregados

La operación de agregación es un concepto muy importante en las consultas a bases de datos. Normalmente se utiliza para análisis estadísticos como sumas, conteos, promedios, valores máximos y mínimos. Estas operaciones ayudan a los usuarios a extraer información significativa de una gran cantidad de datos, brindando soporte para el análisis de datos y la toma de decisiones. Las funciones implementadas en la base de datos para la agregación generalmente se conocen como funciones de agregado.

2. Operaciones de Agregación Básicas

2.1 Concepto de Funciones de Agregado

Las funciones de agregado son funciones utilizadas en lenguajes de consulta de bases de datos para realizar una serie de cálculos y devolver un único valor. En SQL y lenguajes de consulta similares, las funciones de agregado pueden operar en una columna de datos y devolver un único valor, como suma (SUM), promedio (AVG), conteo (COUNT), máximo (MAX) y mínimo (MIN). Cuando necesitamos realizar análisis estadísticos de datos, las funciones de agregado son herramientas importantes para procesar conjuntos de datos y extraer datos estadísticos.

2.2 Agregación de un Solo Campo

En aplicaciones prácticas, el análisis de agregación de un solo campo es un requisito muy común, a menudo utilizado para obtener resultados estadísticos como la suma, el promedio, el máximo y el mínimo de una columna específica. Supongamos que tenemos una tabla de información de pagos y queremos calcular el monto total pagado por los usuarios. Usando el marco ent, podemos construir una consulta desde la entidad y aplicar funciones de agregado de la siguiente manera:

func Do(ctx context.Context, client *ent.Client) {
    // Calcular la suma del campo Monto para la entidad Pago
    suma, err := client.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Error al obtener la suma: %v", err)
    }
    log.Printf("Monto total de los pagos: %d", suma)
}

En el fragmento de código anterior, iniciamos una consulta para la entidad de pago utilizando client.Payment.Query(), luego utilizamos el método Aggregate() para llamar a la función ent.Sum con payment.Amount como parámetro para calcular el monto total de los pagos. Usamos .Int(ctx) para convertir el resultado de la agregación a un número entero y lo registramos.

2.3 Agregación de Múltiples Campos

En muchos casos, necesitamos realizar operaciones de agregación en múltiples campos en lugar de solo uno. En esta sección, demostraremos cómo lograr la agregación de múltiples campos mediante un ejemplo.

En este ejemplo, sumaremos, encontraremos el mínimo, encontraremos el máximo y contaremos el campo Edad en la tabla de mascotas.

func Do(ctx context.Context, client *ent.Client) {
    var v []struct {
        Suma, Min, Max, Cont int
    }
    err := client.Pet.Query().
        Aggregate(
            ent.Sum(pet.FieldAge), // Suma de Edad
            ent.Min(pet.FieldAge), // Edad Mínima
            ent.Max(pet.FieldAge), // Edad Máxima
            ent.Count(),           // Conteo
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Consulta fallida: %v", err)
    }
    // Mostrar todos los resultados de la agregación
    for _, agg := range v {
        fmt.Printf("Suma: %d Mínimo: %d Máximo: %d Conteo: %d\n", agg.Suma, agg.Min, agg.Max, agg.Cont)
    }
}

En el código anterior, utilizamos la función Aggregate para realizar la agregación de múltiples campos y la función Scan para almacenar los resultados de la agregación en la matriz v. Luego, iteramos a través de v para mostrar todos los resultados de la agregación.

3. Aplicación de la Agregación con Group By

3.1. Uso de Group By para Agrupar Campos

En las operaciones de bases de datos, Group By es un método común para agrupar datos. En esta sección, aprenderemos cómo utilizar Group By para agrupar datos en la base de datos.

Ejemplo tutorial, cómo agrupar uno o más campos usando Group By.

Supongamos que tenemos una tabla de usuarios y necesitamos agrupar el campo nombre de los usuarios y calcular el número de usuarios en cada grupo. A continuación se muestra un ejemplo de código de cómo lograr este requisito:

func Do(ctx context.Context, client *ent.Client) {
    nombres, err := client.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Fallo al ejecutar la consulta agrupada: %v", err)
    }
    // Mostrar el nombre de cada grupo
    for _, nombre := range nombres {
        fmt.Println("Nombre del grupo:", nombre)
    }
}

En el código anterior, utilizamos el método GroupBy del generador de consultas para especificar por qué campo queremos agrupar.

3.2. Agrupación y agregación de varios campos

A veces, queremos agrupar datos basados en varios campos y realizar funciones de agregación en cada grupo. A continuación se muestra un ejemplo de cómo lograr este requisito:

El siguiente código muestra cómo agrupar datos en la tabla de usuarios basándose en los campos nombre y edad, y calcular la suma total de edades y el número de usuarios en cada grupo.

func Do(ctx context.Context, client *ent.Client) {
    var v []struct {
        Nombre  string `json:"nombre"`
        Edad   int    `json:"edad"`
        Suma   int    `json:"suma"`
        Conteo int    `json:"conteo"`
    }
    err := client.User.Query().
        GroupBy(user.FieldName, user.FieldAge).
        Aggregate(ent.Count(), ent.Sum(user.FieldAge)).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Error al ejecutar la consulta de agrupación y agregación de múltiples campos: %v", err)
    }
    // Mostrar información detallada para cada grupo
    for _, grupo := range v {
        fmt.Printf("Nombre: %s Edad: %d Suma: %d Conteo: %d\n", grupo.Nombre, grupo.Edad, grupo.Suma, grupo.Conteo)
    }
}

En este ejemplo, no solo agrupamos los datos basados en los campos nombre y edad en la tabla de usuarios, sino que también usamos las funciones de agregación Count y Sum para calcular el número total de registros y la suma total de edades en cada grupo.

4. Combinando el Having con Group By

La cláusula Having filtra los resultados de agregación obtenidos después de la operación Group By. El siguiente ejemplo muestra cómo seleccionar solo los usuarios con la edad máxima en cada rol:

func Do(ctx context.Context, client *ent.Client) {
    var usuarios []struct {
        Id      Int
        Edad     Int
        Rol    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, &usuarios)
    if err != nil {
        log.Fatalf("Error al ejecutar la consulta combinando Having con Group By: %v", err)
    }
    // Mostrar información de usuario que satisface la condición Having
    for _, usuario := range usuarios {
        fmt.Printf("ID: %d Edad: %d Rol: %s\n", usuario.Id, usuario.Edad, usuario.Rol)
    }
}

El código anterior generará una consulta SQL equivalente para seleccionar los usuarios con la edad máxima en cada rol.