Skip to main content

Grouping Strategy

A summary table stores pre-aggregated rows for a number of grouping sets. A grouping set is a combination of dimensions. For example, the grouping set (year, country) stores one row for each year and country, and the grouping set (country) stores one row for each country, with all the years added together.

A query reads the stored grouping set that contains every dimension involved in the query, and aggregates it further if necessary. The grouping strategy decides which grouping sets are stored.

The Default: Every Combination​

By default, a summary stores every combination of its dimensions. This is the cube of the dimensions: each plain dimension is either present or absent, and each dimension group contributes each of its own grouping sets.

This means any combination of dimensions can be queried. But the number of grouping sets multiplies with every added dimension:

DimensionsGrouping sets
4 plain dimensions2⁴ = 16
8 plain dimensions2⁸ = 256
8 plain dimensions and a time hierarchy with 6 levels2⁸ × 6 = 1,536
9 plain dimensions and two time hierarchies with 6 levels each2⁹ × 6 × 6 = 18,432

Each grouping set stores up to one row for every distinct combination of its dimensions in each partition. With many dimensions, especially ones with many distinct values, the summary table can become larger than the source table. Refreshing it also becomes slow: grouping sets over 4,000 are split into batches, and each batch scans the source data again.

Dimension Groups​

The grouping sets inside a dimension group are determined by the grouping strategy of the group, specified in the #[DimensionGroup] attribute:

use Doctrine\ORM\Mapping as ORM;
use Rekalogika\Analytics\Core\Entity\BaseDimensionGroup;
use Rekalogika\Analytics\Core\GroupingStrategy\RollUpStrategy;
use Rekalogika\Analytics\Metadata\Attribute\DimensionGroup;

#[ORM\Embeddable()]
#[DimensionGroup(groupingStrategy: new RollUpStrategy(['year', 'month', 'date']))]
class MyDate extends BaseDimensionGroup
{
// dimensions: year, month, date
}

The built-in strategies, with the grouping sets they produce for the children a, b, and c:

StrategyGrouping sets
RollUpStrategy(a, b, c), (a, b), (a), ()
CubeStrategyevery combination of a, b, and c, 8 in total
FieldSetStrategy(a, b, c) only
GroupingSetStrategy(a), (b), (c), each child separately
ExplicitGroupingSetsStrategyonly the listed sets, see below

All the strategies are in the Rekalogika\Analytics\Core\GroupingStrategy namespace.

A RollUpStrategy is the natural choice for a hierarchy: a month is always stored together with its year, so (month) alone is never stored, but it can be queried from (year, month).

A GroupingSetStrategy is useful for alternative hierarchies that are never combined, like the civil and ISO week calendars in TimeGroup. A query that combines dimensions from two of the alternatives cannot be answered.

Explicit Grouping Sets in a Summary​

If a summary has many dimensions but is only queried in a few known ways, specify the grouping sets to store in the #[Summary] attribute using ExplicitGroupingSetsStrategy:

use Doctrine\ORM\Mapping as ORM;
use Rekalogika\Analytics\Core\Entity\BaseSummary;
use Rekalogika\Analytics\Core\GroupingStrategy\ExplicitGroupingSetsStrategy;
use Rekalogika\Analytics\Metadata\Attribute as Analytics;

#[ORM\Entity()]
#[Analytics\Summary(
sourceClass: Order::class,
groupingStrategy: new ExplicitGroupingSetsStrategy([
['time', 'customerCountry', 'customerType', 'itemCategory'],
['customerRegion'],
['customerGender', 'customerType'],
]),
)]
class OrderSummary extends BaseSummary
{
// ...
}

Each set is a list of the root dimensions of the summary, by their property names. An empty set [] stores the grand total.

A dimension group in a set keeps its own grouping strategy. If time is a roll up of year, month, and date, then the set ['time', 'customerCountry'] stores these grouping sets:

  • (year, month, date, customerCountry)
  • (year, month, customerCountry)
  • (year, customerCountry)
  • (customerCountry)

Duplicate grouping sets, from overlapping sets, are stored only once. An unknown dimension name causes an error when the summary metadata is created.

Choosing a Grouping Set During Query​

A query can select any subset of a stored grouping set. The query engine picks the smallest stored grouping set that contains every dimension involved in the query, including the dimensions used in the query's filter, then aggregates the rows to the requested dimensions.

With the example above:

Query dimensionsGrouping set used
customerType(customerGender, customerType)
customerCountry, itemCategory(customerCountry, customerType, itemCategory)
time.civil.year, filtered by itemCategory(year, customerCountry, customerType, itemCategory)
customerRegion, customerGendernone, an error

If no stored grouping set contains all the dimensions, the query throws GroupingSetNotAvailable. The exception is translatable, and the frontend renderers display its message to the user.

Guidelines​

  • Store one fine grouping set containing every dimension the queries need. Every query that is a subset of it can be answered. Its size is bounded by the number of distinct combinations of its dimensions in each partition, which is usually much smaller than the number of source rows.
  • Add small grouping sets for frequent queries that only need a few dimensions, such as the list of choices in a filter. Otherwise, they are aggregated from the fine grouping set at query time.
  • Keep dimension groups at the granularity the queries actually use. Each level in a hierarchy multiplies the grouping sets.
  • With the default cube, every combination can be queried. With explicit grouping sets, a user interface that allows arbitrary combinations, like the pivot table panel, may produce a combination that is not available.