Nested Aggregation: Looker vs Holistics
Introduction
Every Looker developer has hit this wall: you need a median of a sum, and suddenly you're creating yet another derived table. Your model grows by another view. Your field picker gets more cluttered. And your end users? They're back to filing tickets instead of exploring data themselves.
This post explains why nested aggregations are painful in Looker, examines the workarounds, and shows how Holistics handles them differently.
Looker handles nested aggregation by creating derived tables, which limits self-service capabilities and causes model bloat. Holistics allows you to define metrics with nested aggregation logic directly, and handles the nested SQL automatically.
| Feature | Looker (LookML) | Holistics (AQL) |
|---|---|---|
| Logic Placement | Spread across Views and Derived Tables | Encapsulated entirely within the Metric definition |
| Grain Control | Fixed at the View level (Hardcoded dimensions) | Dynamic—metric defines its own inner grain |
| Self-Service | Limited—new dimensions require new derived tables | High—dimensions can be changed freely in the UI |
| Code Bloat | High (Dozens of "Fact" views for different grains) | Low (One metric handles multiple dimensional contexts) |
The Problem: Median of Sum (Aggregate of Aggregate)
Here's a common business question that's surprisingly hard to answer in Looker:
For each country, give me median revenue per buyer
| Country | Median Revenue Per Buyer |
|---|---|
| United States | $1000 |
| Canada | $600 |
| Mexico | $300 |
In a standard Looker view, your code might look like this:
view: orders {
dimension: country { type: string; sql: ${TABLE}.country ;; }
dimension: user_id { type: string; sql: ${TABLE}.user_id ;; }
measure: revenue {
type: sum
sql: ${TABLE}.revenue ;;
}
}
The problem? You cannot create a measure of type: median that points to a measure of type: sum. Looker won't let you nest aggregations this way.
The SQL Solution
Because SUM and MEDIAN operate on different "grains" of data, SQL requires two distinct steps:
- Step 1 (inner sum): Group by User and Country to get the
SUM(revenue). - Step 2 (outer median): Take the
MEDIANof those results, grouped only by Country.
Standard SQL expresses this through subqueries/CTE:
-- For each country, display median revenue per user
WITH cte AS (
SELECT
country,
user_id,
SUM(revenue) AS revenue
FROM orders
GROUP BY 1,2
)
SELECT
country,
percentile_cont(0.5) WITHIN GROUP (ORDER BY revenue) AS median_revenue_per_user
FROM cte
GROUP BY 1;