zoobzio December 14, 2025 Edit this page

Aggregates

Soy provides aggregate functions for counting and computing statistics. All aggregates return float64 to handle various numeric types uniformly.

See API Reference for complete method signatures.

COUNT

Count All Rows

count, err := users.Count().Exec(ctx, nil)
// count is float64
fmt.Printf("Total users: %.0f\n", count)

Count with WHERE

count, err := users.Count().
    Where("status", "=", "status").
    Exec(ctx, map[string]any{"status": "active"})

SUM

Sum a numeric field:

total, err := orders.Sum("amount").
    Where("status", "=", "status").
    Exec(ctx, map[string]any{"status": "completed"})

fmt.Printf("Total revenue: $%.2f\n", total)

AVG

Calculate average:

avgAge, err := users.Avg("age").
    Where("status", "=", "status").
    Exec(ctx, map[string]any{"status": "active"})

MIN and MAX

// Minimum
minPrice, err := products.Min("price").Exec(ctx, nil)

// Maximum
maxScore, err := scores.Max("value").
    Where("game_id", "=", "game").
    Exec(ctx, map[string]any{"game": 123})

NULL Handling

Aggregate functions return 0.0 when the result is NULL (e.g., no matching rows):

sum, err := orders.Sum("amount").
    Where("user_id", "=", "user").
    Exec(ctx, map[string]any{"user": 999})  // User with no orders

// sum == 0.0, err == nil

GROUP BY

Use Query with aggregate expressions for grouped results:

type StatusCount struct {
    Status string `db:"status"`
    Count  int    `db:"count"`
}

// Note: This requires a custom query or raw SQL
// Soy aggregates return single values

For grouped aggregates, use the Query builder with custom fields:

results, err := users.Query().
    Fields("status", "COUNT(*) as count").
    GroupBy("status").
    Exec(ctx, nil)

HAVING

Filter aggregate results:

results, err := orders.Query().
    Fields("customer_id", "SUM(amount) as total").
    GroupBy("customer_id").
    Having("total", ">=", "min_total").
    Exec(ctx, map[string]any{"min_total": 1000})

Aggregate HAVING

Use aggregate functions in HAVING:

results, err := orders.Query().
    Fields("customer_id").
    GroupBy("customer_id").
    HavingAgg("COUNT", "*", ">=", "min_orders").
    Exec(ctx, map[string]any{"min_orders": 5})
SELECT customer_id FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= :min_orders

Combined WHERE and HAVING

results, err := orders.Query().
    Fields("customer_id", "SUM(amount) as total").
    Where("created_at", ">=", "start_date").
    GroupBy("customer_id").
    HavingAgg("SUM", "amount", ">=", "threshold").
    OrderBy("total", "desc").
    Limit(10).
    Exec(ctx, map[string]any{
        "start_date": time.Now().AddDate(0, -1, 0),
        "threshold":  500,
    })

Transactions

Aggregates support transaction execution:

tx, err := db.BeginTxx(ctx, nil)
if err != nil {
    return err
}
defer tx.Rollback()

count, err := orders.Count().
    Where("user_id", "=", "user").
    ExecTx(ctx, tx, params)
if err != nil {
    return err
}

// Use count in subsequent operations...

Inspecting SQL

result, err := users.Count().
    Where("status", "=", "status").
    Render()

fmt.Println(result.SQL)
// SELECT COUNT(*) FROM users WHERE status = :status

Complete Example

// Dashboard statistics
func GetDashboardStats(ctx context.Context, userID int) (*Stats, error) {
    params := map[string]any{"user_id": userID}

    totalOrders, err := orders.Count().
        Where("user_id", "=", "user_id").
        Exec(ctx, params)
    if err != nil {
        return nil, err
    }

    totalSpent, err := orders.Sum("amount").
        Where("user_id", "=", "user_id").
        Where("status", "=", "status").
        Exec(ctx, map[string]any{
            "user_id": userID,
            "status":  "completed",
        })
    if err != nil {
        return nil, err
    }

    avgOrderValue, err := orders.Avg("amount").
        Where("user_id", "=", "user_id").
        Exec(ctx, params)
    if err != nil {
        return nil, err
    }

    return &Stats{
        TotalOrders:   int(totalOrders),
        TotalSpent:    totalSpent,
        AvgOrderValue: avgOrderValue,
    }, nil
}