Skip to content

GROUP BY Operator

The GROUP BY operator groups items by one or more fields and applies aggregation functions to calculate statistics for each group.

The GROUP BY operator enables you to:

  • Group items by one or more fields
  • Calculate aggregations (COUNT, SUM, AVG, MIN, MAX, FIRST, LAST, COLLECT, CONCAT)
  • Filter groups using HAVING conditions
  • Access aggregated values in template expressions

Key Features:

  • 9 built-in aggregation functions
  • Single or multiple field grouping
  • HAVING clause with all comparison operators
  • Aggregated values available in template expressions
  • Works with all other wildcard operators
$template = [
'result' => [
'GROUP BY' => [
'field' => '{{ source.*.grouping_field }}',
'aggregations' => [
'agg_name' => ['FUNCTION', '{{ source.*.field_to_aggregate }}'],
],
],
'*' => [
'group_field' => '{{ source.*.grouping_field }}',
'aggregated_value' => '{{ source.*.agg_name }}',
],
],
];
'GROUP BY' => [
// Required: Field(s) to group by
'field' => '{{ source.*.field }}', // Single field (string)
// OR
'field' => [ // Multiple fields (array)
'{{ source.*.field1 }}',
'{{ source.*.field2 }}',
],
// OR
'fields' => '{{ source.*.field }}', // Single field (string)
// OR
'fields' => [ // Multiple fields (array)
'{{ source.*.field1 }}',
'{{ source.*.field2 }}',
],
// Optional: Aggregations to calculate
'aggregations' => [
'result_name' => ['FUNCTION', '{{ source.*.field }}', ...args],
],
// Optional: Filter groups after aggregation
'HAVING' => [
'aggregation_name' => ['operator', value],
],
]

Note: Both field and fields are supported and can accept either a single string or an array of strings.

Counts the number of items in each group.

'aggregations' => [
'total_count' => ['COUNT'],
]

Sums numeric values.

'aggregations' => [
'total_amount' => ['SUM', '{{ orders.*.amount }}'],
]

Calculates average of numeric values.

'aggregations' => [
'avg_price' => ['AVG', '{{ products.*.price }}'],
]

Finds minimum value.

'aggregations' => [
'min_price' => ['MIN', '{{ products.*.price }}'],
]

Finds maximum value.

'aggregations' => [
'max_price' => ['MAX', '{{ products.*.price }}'],
]

Gets first value in group.

'aggregations' => [
'first_order' => ['FIRST', '{{ orders.*.date }}'],
]

Gets last value in group.

'aggregations' => [
'last_order' => ['LAST', '{{ orders.*.date }}'],
]

Collects all values into an array.

'aggregations' => [
'all_names' => ['COLLECT', '{{ items.*.name }}'],
]

Concatenates values with separator.

'aggregations' => [
'names_list' => ['CONCAT', '{{ items.*.name }}', ', '],
]

Filter groups after aggregation:

'GROUP BY' => [
'field' => '{{ orders.*.customerId }}',
'aggregations' => [
'total_orders' => ['COUNT'],
'total_spent' => ['SUM', '{{ orders.*.amount }}'],
],
'HAVING' => [
'total_orders' => ['>', 5],
'total_spent' => ['>=', 1000],
],
]
  • = - Equal
  • !=, <> - Not equal
  • > - Greater than
  • < - Less than
  • >= - Greater than or equal
  • <= - Less than or equal

Group by multiple fields:

'GROUP BY' => [
'fields' => [
'{{ orders.*.customerId }}',
'{{ orders.*.status }}',
],
'aggregations' => [
'count' => ['COUNT'],
],
]
$sources = [
'orders' => [
['customerId' => 1, 'amount' => 100],
['customerId' => 1, 'amount' => 200],
['customerId' => 2, 'amount' => 150],
],
];
$template = [
'result' => [
'GROUP BY' => [
'field' => '{{ orders.*.customerId }}',
'aggregations' => [
'total_orders' => ['COUNT'],
'total_spent' => ['SUM', '{{ orders.*.amount }}'],
'avg_order' => ['AVG', '{{ orders.*.amount }}'],
],
],
'*' => [
'customerId' => '{{ orders.*.customerId }}',
'totalOrders' => '{{ orders.*.total_orders }}',
'totalSpent' => '{{ orders.*.total_spent }}',
'avgOrder' => '{{ orders.*.avg_order }}',
],
],
];
$result = DataMapper::mapFromTemplate($template, $sources);
// Result:
// [
// 'result' => [
// ['customerId' => 1, 'totalOrders' => 2, 'totalSpent' => 300, 'avgOrder' => 150],
// ['customerId' => 2, 'totalOrders' => 1, 'totalSpent' => 150, 'avgOrder' => 150],
// ]
// ]
$sources = [
'products' => [
['category' => 'Electronics', 'price' => 1299, 'name' => 'Laptop'],
['category' => 'Electronics', 'price' => 29, 'name' => 'Mouse'],
['category' => 'Furniture', 'price' => 299, 'name' => 'Desk'],
],
];
$template = [
'categories' => [
'GROUP BY' => [
'field' => '{{ products.*.category }}',
'aggregations' => [
'count' => ['COUNT'],
'min_price' => ['MIN', '{{ products.*.price }}'],
'max_price' => ['MAX', '{{ products.*.price }}'],
'avg_price' => ['AVG', '{{ products.*.price }}'],
'product_names' => ['CONCAT', '{{ products.*.name }}', ', '],
],
],
'*' => [
'category' => '{{ products.*.category }}',
'productCount' => '{{ products.*.count }}',
'priceRange' => '{{ products.*.min_price }} - {{ products.*.max_price }}',
'avgPrice' => '{{ products.*.avg_price }}',
'products' => '{{ products.*.product_names }}',
],
],
];
$template = [
'highValueCustomers' => [
'GROUP BY' => [
'field' => '{{ orders.*.customerId }}',
'aggregations' => [
'total_orders' => ['COUNT'],
'total_spent' => ['SUM', '{{ orders.*.amount }}'],
],
'HAVING' => [
'total_orders' => ['>', 5],
'total_spent' => ['>=', 1000],
],
],
'*' => [
'customerId' => '{{ orders.*.customerId }}',
'totalOrders' => '{{ orders.*.total_orders }}',
'totalSpent' => '{{ orders.*.total_spent }}',
],
],
];
// ✅ Good
'aggregations' => [
'total_orders' => ['COUNT'],
'total_revenue' => ['SUM', '{{ orders.*.amount }}'],
]
// ❌ Bad
'aggregations' => [
'cnt' => ['COUNT'],
'sum' => ['SUM', '{{ orders.*.amount }}'],
]
// ✅ Good - Filter first, then group
$template = [
'result' => [
'WHERE' => [
'{{ orders.*.status }}' => ['=', 'completed'],
],
'GROUP BY' => [
'field' => '{{ orders.*.customerId }}',
// ...
],
],
];
// ✅ Good - Filter aggregated values with HAVING
'HAVING' => [
'total_orders' => ['>', 5],
]
// ❌ Bad - Can't filter aggregated values with WHERE
'WHERE' => [
'{{ orders.*.total_orders }}' => ['>', 5], // Won't work
]