Skip to content

Multi-level Grouping

This guide demonstrates how to build deeply nested aggregations, like grouping venues by region → city → venue.

The Scenario

You have venue data with location hierarchy and want to compute aggregates at each level.

Data Type

typescript
interface Venue {
  venueId: string;
  region: string;
  city: string;
  venueName: string;
  capacity: number;
}

Building the Hierarchy

typescript
import { createPipeline } from "aggregator-toy";

const builder = createPipeline<Venue>()
  // Level 1: Group by region
  .groupBy(["region"], "byRegion")
  
  // Level 2: Within each region, group by city
  .in("byRegion").groupBy(["city"], "cities")
  
  // Aggregates at city level
  .in("byRegion", "cities").count("items", "venueCount")
  .in("byRegion", "cities").sum("items", "capacity", "totalCapacity")
  .in("byRegion", "cities").max("items", "capacity", "largestVenue")
  
  // Aggregates at region level
  .in("byRegion").count("cities", "cityCount")
  .in("byRegion").sum("cities", "totalCapacity", "regionCapacity");

Feeding Data

typescript
const typeDescriptor = builder.getTypeDescriptor();
let state = [];
const pipeline = builder.build(
  transform => { state = transform(state); },
  typeDescriptor
);

pipeline.add("v1", { 
  venueId: "v1", region: "Southwest", city: "Dallas", 
  venueName: "Arena", capacity: 20000 
});
pipeline.add("v2", { 
  venueId: "v2", region: "Southwest", city: "Dallas", 
  venueName: "Stadium", capacity: 50000 
});
pipeline.add("v3", { 
  venueId: "v3", region: "Southwest", city: "Houston", 
  venueName: "Center", capacity: 18000 
});
pipeline.add("v4", { 
  venueId: "v4", region: "West", city: "Los Angeles", 
  venueName: "Coliseum", capacity: 78000 
});

Result Structure

typescript
// state.byRegion
[
  {
    key: "Southwest",
    value: {
      region: "Southwest",
      cityCount: 2,
      regionCapacity: 88000,
      cities: [
        {
          key: "Dallas",
          value: {
            city: "Dallas",
            venueCount: 2,
            totalCapacity: 70000,
            largestVenue: 50000,
            items: [/* venue objects */]
          }
        },
        {
          key: "Houston",
          value: {
            city: "Houston",
            venueCount: 1,
            totalCapacity: 18000,
            largestVenue: 18000,
            items: [/* venue objects */]
          }
        }
      ]
    }
  },
  {
    key: "West",
    value: {
      region: "West",
      cityCount: 1,
      regionCapacity: 78000,
      cities: [
        {
          key: "Los Angeles",
          value: {
            city: "Los Angeles",
            venueCount: 1,
            totalCapacity: 78000,
            largestVenue: 78000,
            items: [/* venue objects */]
          }
        }
      ]
    }
  }
]

Rendering the Hierarchy

tsx
function VenueHierarchy({ state }) {
  return (
    <div>
      {state.byRegion.map(region => (
        <div key={region.key} className="region">
          <h2>{region.value.region}</h2>
          <p>
            {region.value.cityCount} cities, 
            {region.value.regionCapacity.toLocaleString()} total capacity
          </p>
          
          {region.value.cities.map(city => (
            <div key={city.key} className="city">
              <h3>{city.value.city}</h3>
              <p>
                {city.value.venueCount} venues, 
                {city.value.totalCapacity.toLocaleString()} capacity
              </p>
              
              <ul>
                {city.value.items.map(venue => (
                  <li key={venue.key}>
                    {venue.value.venueName} ({venue.value.capacity.toLocaleString()})
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

Adding More Levels

You can nest as deeply as needed:

typescript
// Country → Region → City → District → Venue
const builder = createPipeline<DetailedVenue>()
  .groupBy(["country"], "byCountry")
  .in("byCountry").groupBy(["region"], "regions")
  .in("byCountry", "regions").groupBy(["city"], "cities")
  .in("byCountry", "regions", "cities").groupBy(["district"], "districts")
  // Aggregates bubble up
  .in("byCountry", "regions", "cities", "districts").count("items", "venueCount");

Performance Considerations

  • Each level adds some overhead
  • 3-4 levels is typical for most use cases
  • Consider if you really need all levels, or if some can be computed on render

Released under the MIT License.