1. Giới thiệu về Phân tích Tổng hợp

Phép nghiệm tổng hợp là một khái niệm rất quan trọng trong các truy vấn cơ sở dữ liệu. Thông thường nó được sử dụng cho phân tích thống kê như tổng, đếm, trung bình, giá trị tối đa và tối thiểu. Những phép nghiệm này giúp người dùng rút trích thông tin ý nghĩa từ một lượng lớn dữ liệu, hỗ trợ cho việc phân tích dữ liệu và ra quyết định. Các chức năng được triển khai trong cơ sở dữ liệu cho phép tổng hợp thường được gọi là các hàm tổng hợp.

2. Các Phép Tổng hợp Cơ bản

2.1 Khái niệm về Hàm Tổng hợp

Hàm tổng hợp là các hàm được sử dụng trong ngôn ngữ truy vấn cơ sở dữ liệu để thực hiện một loạt các tính toán và trả về một giá trị duy nhất. Trong SQL và các ngôn ngữ truy vấn tương tự, hàm tổng hợp có thể hoạt động trên một cột dữ liệu và trả về một giá trị duy nhất, như tổng (SUM), trung bình (AVG), đếm (COUNT), tối đa (MAX) và tối thiểu (MIN). Khi chúng ta cần thực hiện phân tích thống kê dữ liệu, hàm tổng hợp là công cụ quan trọng cho việc xử lý tập dữ liệu và trích xuất dữ liệu thống kê.

2.2 Tổng hợp Trên Một Trường Dữ liệu

Trong ứng dụng thực tế, phân tích tổng hợp trên một trường dữ liệu là một yêu cầu rất phổ biến, thường được sử dụng để lấy kết quả thống kê như tổng, trung bình, giá trị tối đa và tối thiểu của một cột cụ thể. Giả sử chúng ta có một bảng thông tin thanh toán, và chúng ta muốn tính tổng số tiền thanh toán bởi người dùng. Sử dụng framework ent, chúng ta có thể tạo một truy vấn từ thực thể và áp dụng các hàm tổng hợp như sau:

func Do(ctx context.Context, client *ent.Client) {
    // Tính tổng trường Số tiền thanh toán cho thực thể Payment
    sum, err := client.Payment.Query().
        Aggregate(
            ent.Sum(payment.Amount),
        ).
        Int(ctx)
    if err != nil {
        log.Fatalf("Không thể lấy tổng: %v", err)
    }
    log.Printf("Tổng số tiền thanh toán: %d", sum)
}

Trong đoạn code trên, chúng ta khởi tạo một truy vấn cho thực thể thanh toán bằng cách sử dụng client.Payment.Query(), sau đó sử dụng phương thức Aggregate() để gọi hàm ent.Sum với payment.Amount là tham số để tính tổng số tiền thanh toán. Chúng ta sử dụng .Int(ctx) để chuyển kết quả tổng hợp thành một số nguyên và ghi log nó.

2.3 Tổng hợp Trên Nhiều Trường Dữ liệu

Trong nhiều trường hợp, chúng ta cần thực hiện các phép tổng hợp trên nhiều trường dữ liệu thay vì chỉ một trường. Trong phần này, chúng ta sẽ minh họa cách thức thực hiện tổng hợp trên nhiều trường thông qua một ví dụ.

Trong ví dụ này, chúng ta sẽ tính tổng, tìm giá trị tối thiểu, tìm giá trị tối đa và đếm trường Age trong bảng Pet.

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), // Tổng của Age
            ent.Min(pet.FieldAge), // Tuổi tối thiểu
            ent.Max(pet.FieldAge), // Tuổi tối đa
            ent.Count(),           // Đếm
        ).
        Scan(ctx, &v)
    if err != nil {
        log.Fatalf("Truy vấn thất bại: %v", err)
    }
    // Xuất tất cả kết quả tổng hợp
    for _, agg := range v {
        fmt.Printf("Tổng: %d Tối thiểu: %d Tối đa: %d Đếm: %d\n", agg.Sum, agg.Min, agg.Max, agg.Count)
    }
}

Trong đoạn mã trên, chúng ta sử dụng hàm Aggregate để thực hiện tổng hợp trên nhiều trường, và sử dụng hàm Scan để lưu trữ kết quả tổng hợp vào slice v. Sau đó, chúng ta lặp qua v để xuất tất cả kết quả tổng hợp.

3. Ứng dụng của Phân tích Tổng hợp theo Nhóm

3.1. Sử dụng Group By để Nhóm Trường

Trong các hoạt động cơ sở dữ liệu, Group By là một phương pháp phổ biến để nhóm dữ liệu. Trong phần này, chúng ta sẽ tìm hiểu cách sử dụng Group By để nhóm dữ liệu trong cơ sở dữ liệu.

Ví dụ hướng dẫn, cách nhóm một hoặc nhiều trường bằng Group By.

Giả sử chúng ta có một bảng người dùng và chúng ta cần nhóm trường name của người dùng và tính số lượng người dùng trong mỗi nhóm. Dưới đây là một ví dụ code về cách thực hiện yêu cầu này:

func Do(ctx context.Context, client *ent.Client) {
    names, err := client.User.
        Query().
        GroupBy(user.FieldName).
        Strings(ctx)
    if err != nil {
        log.Fatalf("Không thể thực hiện truy vấn nhóm: %v", err)
    }
    // Xuất tên của mỗi nhóm
    for _, name := range names {
        fmt.Println("Tên nhóm:", name)
    }
}

Trong đoạn code trên, chúng ta sử dụng phương thức GroupBy của query builder để chỉ định trường chúng ta muốn nhóm theo.

3.2. Nhóm và Tổng hợp Nhiều Trường

Đôi khi, chúng ta muốn nhóm dữ liệu dựa trên nhiều trường và thực hiện các chức năng tổng hợp trên mỗi nhóm. Dưới đây là một ví dụ về cách thực hiện yêu cầu này:

Đoạn mã dưới đây thể hiện cách nhóm dữ liệu trong bảng người dùng dựa trên các trường têntuổi, và tính tổng tuổi và số người dùng trong mỗi nhóm.

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("Lỗi khi thực hiện truy vấn nhóm và tổng hợp với nhiều trường: %v", err)
    }
    // Đầu ra thông tin chi tiết cho mỗi nhóm
    for _, group := range v {
        fmt.Printf("Tên: %s Tuổi: %d Tổng: %d Số lượng: %d\n", group.Name, group.Age, group.Sum, group.Count)
    }
}

Trong ví dụ này, chúng ta không chỉ nhóm dữ liệu dựa trên các trường têntuổi trong bảng người dùng, mà còn sử dụng các hàm tổng hợp CountSum để tính tổng số bản ghi và tổng tuổi trong mỗi nhóm.

4. Kết hợp Having với Group By

Câu lệnh Having lọc kết quả tổng hợp được sau khi thực hiện phép Group By. Ví dụ dưới đây cho thấy cách chỉ chọn các người dùng có tuổi lớn nhất trong mỗi vai trò:

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("Lỗi khi thực hiện truy vấn Having kết hợp với Group By: %v", err)
    }
    // Đầu ra thông tin người dùng thỏa điều kiện Having
    for _, user := range users {
        fmt.Printf("ID: %d Tuổi: %d Vai trò: %s\n", user.Id, user.Age, user.Role)
    }
}

Đoạn mã trên sẽ tạo ra một truy vấn SQL tương đương để chọn các người dùng có tuổi lớn nhất trong mỗi vai trò.