Skip to content

Grouping & Aggregation

Group documents and compute aggregated values.

Simple Grouping

Group by a single field:

konduct.collection<Order>()
    .group {
        by(Order::status)
        accumulate {
            "count" count Unit
            "totalAmount" sum Order::amount
        }
    }
    .toList()

Result structure:

{
    _id: "completed",
    status: "completed",  // Auto-added
    count: 150,
    totalAmount: 45000.0
}

Composite Key Grouping

Group by multiple fields:

konduct.collection<Sale>()
    .group {
        by {
            "category" from Sale::category
            "region" from Sale::region
        }
        accumulate {
            "totalSales" sum Sale::amount
            "avgSale" avg Sale::amount
        }
    }
    .toList()

Result:

{
    _id: { category: "Electronics", region: "North" },
    category: "Electronics",  // Auto-added
    region: "North",          // Auto-added
    totalSales: 125000.0,
    avgSale: 450.0
}

Time-Based Grouping

Group by time units:

konduct.collection<Sale>()
    .group {
        by(Sale::date, unit = TimeUnit.MONTH)
        accumulate {
            "monthlySales" sum Sale::amount
            "orderCount" count Unit
        }
    }
    .toList()

Expression-Based Aggregation

Use expressions in accumulators:

konduct.collection<OrderItem>()
    .group {
        by(OrderItem::orderId)
        accumulate {
            "totalRevenue" sum (OrderItem::quantity * OrderItem::price)
            "totalDiscount" sum ((OrderItem::originalPrice - OrderItem::salePrice) * OrderItem::quantity)
            "itemCount" count Unit
        }
    }
    .toList()

All Accumulators

Numeric Aggregations

accumulate {
    "total" sum Product::price
    "average" avg Product::price
    "minimum" min Product::price
    "maximum" max Product::price
}

Counting

accumulate {
    "count" count Unit
    "uniqueCustomers" countDistinct Order::customerId
}

Array Accumulators

accumulate {
    "allNames" push Product::name
    "uniqueTags" addToSet Product::tags
}

First & Last

konduct.collection<Order>()
    .sort { Order::orderDate.asc() }
    .group {
        by(Order::customerId)
        accumulate {
            "firstOrder" first Order::orderDate
            "lastOrder" last Order::orderDate
        }
    }
    .toList()

Typed Results

Get type-safe results:

data class CategoryStats(
    val _id: String,
    val category: String,
    val count: Int,
    val avgPrice: Double,
    val totalRevenue: Double
)

val results: List<CategoryStats> = konduct.collection<Product>()
    .group<Product, CategoryStats> {
        by(Product::category)
        accumulate {
            "count" count Unit
            "avgPrice" avg Product::price
            "totalRevenue" sum Product::price
        }
    }
    .toList()

Or use into():

val results = konduct.collection<Product>()
    .group {
        by(Product::category)
        accumulate {
            "count" count Unit
            "avgPrice" avg Product::price
        }
    }
    .into<CategoryStats>()
    .toList()

Real-World Examples

Customer Lifetime Value

konduct.collection<Order>()
    .match { Order::status eq "completed" }
    .group {
        by(Order::customerId)
        accumulate {
            "totalSpent" sum Order::total
            "orderCount" count Unit
            "avgOrderValue" avg Order::total
            "firstOrderDate" min Order::orderDate
            "lastOrderDate" max Order::orderDate
        }
    }
    .match { "totalSpent" gte 1000 }
    .sort { "totalSpent".desc() }
    .into<CustomerLTV>()
    .toList()

Sales by Category and Month

konduct.collection<Sale>()
    .group {
        by {
            "category" from Sale::category
            "month" from Sale::date.month()
            "year" from Sale::date.year()
        }
        accumulate {
            "revenue" sum (Sale::quantity * Sale::price)
            "units" sum Sale::quantity
            "transactions" count Unit
        }
    }
    .sort {
        "year".desc()
        "month".desc()
        "revenue".desc()
    }
    .toList()

See Also