1. Introdução à Análise de Agregação

A operação de agregação é um conceito muito importante em consultas de banco de dados. Geralmente é usada para análises estatísticas, como soma, contagem, média, valores máximos e mínimos. Essas operações ajudam os usuários a extrair informações significativas de uma grande quantidade de dados, fornecendo suporte para análise de dados e tomada de decisões. As funções implementadas no banco de dados para agregação são geralmente referidas como funções de agregação.

2. Operações de Agregação Básicas

2.1 Conceito de Funções de Agregação

Funções de agregação são funções utilizadas em linguagens de consulta de banco de dados para realizar uma série de cálculos e retornar um único valor. Em SQL e linguagens de consulta similares, as funções de agregação podem operar em uma coluna de dados e retornar um único valor, como soma (SUM), média (AVG), contagem (COUNT), máximo (MAX) e mínimo (MIN). Quando precisamos realizar análises estatísticas de dados, as funções de agregação são ferramentas importantes para processar conjuntos de dados e extrair dados estatísticos.

2.2 Agregação de Campo Único

Nas aplicações práticas, a análise de agregação de campo único é um requisito muito comum, frequentemente utilizado para obter resultados estatísticos, como a soma, média, valor máximo e mínimo de uma coluna específica. Suponha que tenhamos uma tabela de informações de pagamentos e desejamos calcular o valor total pago pelos usuários. Usando o framework ent, podemos construir uma consulta a partir da entidade e aplicar funções de agregação da seguinte forma:

func Executar(ctx context.Context, cliente *ent.Client) {
    // Calcular a soma do campo Amount para a entidade de Pagamento
    soma, err := cliente.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Falha ao obter a soma: %v", err)
    }
    log.Printf("Valor total dos pagamentos: %d", soma)
}

No trecho de código acima, iniciamos uma consulta para a entidade de pagamento usando cliente.Payment.Query(), em seguida, utilizamos o método Aggregate() para chamar a função ent.Sum com payment.Amount como parâmetro para calcular o valor total dos pagamentos. Usamos .Int(ctx) para converter o resultado da agregação em um inteiro e registrá-lo.

2.3 Agregação de Múltiplos Campos

Em muitos casos, precisamos realizar operações de agregação em múltiplos campos em vez de apenas um. Nesta seção, demonstraremos como alcançar a agregação de múltiplos campos por meio de um exemplo.

Neste exemplo, iremos somar, encontrar o mínimo, encontrar o máximo e contar o campo Age na tabela de animais de estimação.

func Executar(ctx context.Context, cliente *ent.Client) {
    var v []struct {
        Soma, Min, Max, Contagem int
    }
    err := cliente.Pet.Query().
        Aggregate(
            ent.Sum(pet.FieldAge), // Soma da Idade
            ent.Min(pet.FieldAge), // Idade Mínima
            ent.Max(pet.FieldAge), // Idade Máxima
            ent.Count(),           // Contagem
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Consulta falhou: %v", err)
    }
    // Exibir todos os resultados agregados
    for _, agg := range v {
        fmt.Printf("Soma: %d Mínimo: %d Máximo: %d Contagem: %d\n", agg.Soma, agg.Min, agg.Max, agg.Contagem)
    }
}

No código acima, utilizamos a função Aggregate para realizar a agregação de vários campos e a função Scan para armazenar os resultados da agregação na slice v. Em seguida, iteramos através de v para exibir todos os resultados agregados.

3. Aplicação do Group By na Agregação

3.1. Utilizando Group By para Agrupar Campos

Nas operações de banco de dados, o Group By é um método comum para agrupar dados. Nesta seção, aprenderemos como usar o Group By para agrupar dados no banco de dados.

Exemplo de tutorial, como agrupar um ou mais campos usando o Group By.

Supondo que tenhamos uma tabela de usuários e precisamos agrupar o campo nome dos usuários e calcular o número de usuários em cada grupo. Abaixo está um exemplo de código de como atender a este requisito:

func Executar(ctx context.Context, cliente *ent.Client) {
    nomes, err := cliente.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Falha ao executar a consulta agrupada: %v", err)
    }
    // Exibir o nome de cada grupo
    for _, nome := range nomes {
        fmt.Println("Nome do grupo:", nome)
    }
}

No código acima, utilizamos o método GroupBy do construtor de consulta para especificar por qual campo queremos agrupar.

3.2. Agrupamento e Agregação de Múltiplos Campos

Às vezes, queremos agrupar dados com base em múltiplos campos e realizar funções de agregação em cada grupo. Abaixo está um exemplo de como alcançar esse requisito:

O código a seguir demonstra como agrupar dados na tabela de usuário com base nos campos name e age, e calcular a idade total e o número de usuários em cada grupo.

func Fazer(ctx context.Context, cliente *ent.Cliente) {
    var v []struct {
        Nome  string `json:"name"`
        Idade int    `json:"age"`
        Soma  int    `json:"sum"`
        Contagem int    `json:"count"`
    }
    err := cliente.User.Query().
        GroupBy(user.FieldName, user.FieldAge).
        Aggregate(ent.Count(), ent.Sum(user.FieldAge)).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Falha ao executar a consulta de agrupamento e agregação de múltiplos campos: %v", err)
    }
    // Saída de informações detalhadas para cada grupo
    for _, grupo := range v {
        fmt.Printf("Nome: %s Idade: %d Soma: %d Contagem: %d\n", grupo.Nome, grupo.Idade, grupo.Soma, grupo.Contagem)
    }
}

Neste exemplo, não apenas agrupamos os dados com base nos campos name e age na tabela de usuário, mas também usamos as funções de agregação Count e Sum para calcular o número total de registros e a idade total em cada grupo.

4. Combinando Having com Group By

A cláusula Having filtra os resultados de agregação obtidos após a operação Group By. O exemplo a seguir mostra como selecionar apenas os usuários com a idade máxima em cada cargo:

func Fazer(ctx context.Context, cliente *ent.Cliente) {
    var usuarios []struct {
        Id      Int
        Idade     Int
        Cargo    string
    }
    err := cliente.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("Falha ao executar a consulta combinando Having com Group By: %v", err)
    }
    // Saída das informações do usuário que satisfazem a condição Having
    for _, usuario := range usuarios {
        fmt.Printf("ID: %d Idade: %d Cargo: %s\n", usuario.Id, usuario.Idade, usuario.Cargo)
    }
}

O código acima irá gerar uma consulta SQL equivalente para selecionar os usuários com a idade máxima em cada cargo.