Relix

Problem solving

How many, how much, per what

Grain: one row per group · Class: Summary · Signals: how many, how much, total, average, per, running total, per interval · Operators: γ, ROLLING, DOWNSAMPLE

The problem

"How many orders and how much money per region? What is each region's running total through the morning? And how much came in per five-minute window?"

How to recognise it

The question says how many, how much, total, average, or carries a per — per region, per day, per customer. The answer has fewer rows than the input: many rows go in, one row per group comes out. That collapse is the signature of a summary, and the group is the grain.

Three shapes, and the per what tells them apart:

The data

Order events through one morning. An inline table's columns are STRING or NUMBER, so the timestamp is converted to a real TIMESTAMP first — DOWNSAMPLE needs one to align its windows.

Orders := [
| at                   | region | amount |
|----------------------|--------|--------|
| 2026-06-01T09:02:00Z | East   | 100    |
| 2026-06-01T09:05:00Z | East   | 120    |
| 2026-06-01T09:11:00Z | West   | 80     |
| 2026-06-01T09:13:00Z | East   | 90     |
| 2026-06-01T09:14:00Z | West   | 95     |
];

Typed := { π to_timestamp(at) → at, region, amount (Orders) };

Recipe 1: one row per group (γ)

γ lists the grouping keys, then the aggregates, each named. COUNT(*) counts rows; SUM/AVG/MIN/MAX skip NULLs.

Query
query { γ region, COUNT(*) → orders, SUM(amount) → total, AVG(amount) → mean (Typed) };
Result
 region  orders  total  mean
 ──────  ──────  ─────  ──────────────
 East         3    310  103.3333333333
 West         2    175            87.5
(2 rows)

The keys are the grain. γ region gives one row per region; add a key and the grain gets finer — one row per region and something else. Decide "one row per ___" before you write the γ, and the keys follow.

Recipe 2: a running total, keeping every row (ROLLING)

A running total is not a collapse — every order stays, and each carries the total so far. ROLLING … OVER ALL ROWS is the cumulative frame; SORT orders it and PER restarts it per group.

Query
query { ROLLING SUM(amount) OVER ALL ROWS SORT at ASC PER region AS running (Typed) };
Result
 at                    region  amount  running
 ────────────────────  ──────  ──────  ───────
 2026-06-01T09:02:00Z  East       100      100
 2026-06-01T09:05:00Z  East       120      220
 2026-06-01T09:13:00Z  East        90      310
 2026-06-01T09:11:00Z  West        80       80
 2026-06-01T09:14:00Z  West        95      175
(5 rows)

OVER n ROWS is the other frame — a trailing window of n rows, for a moving average.

Recipe 3: per time interval (DOWNSAMPLE)

How much per five minutes? is a group whose key is a time bucket. DOWNSAMPLE floors each timestamp to the start of its window and consolidates within it.

Query
query { DOWNSAMPLE at BY '5m' USING SUM (Typed) };
Result
 bucket                sum_amount
 ────────────────────  ──────────
 2026-06-01T09:00:00Z         100
 2026-06-01T09:05:00Z         120
 2026-06-01T09:10:00Z         265
(3 rows)

AVG/SUM keep the numeric columns only — there is no average of a region name — so region is dropped unless it is a grouping key. Add PER to keep it and bucket within each region:

Query
query { DOWNSAMPLE at BY '5m' USING SUM PER region (Typed) };
Result
 region  bucket                sum_amount
 ──────  ────────────────────  ──────────
 East    2026-06-01T09:00:00Z         100
 East    2026-06-01T09:05:00Z         120
 West    2026-06-01T09:10:00Z         175
 East    2026-06-01T09:10:00Z          90
(4 rows)

Variations

Pitfalls

Check it