Skip to content

Commit 5f30556

Browse files
committed
Add GROUPING SETS, ROLLUP, and CUBE documentation
Documents the new SQL:1999 grouping sets support. Adds a main reference page with syntax, examples, SAMPLE BY integration, and limitations. Includes CUBE and ROLLUP redirect pages, sidebar entries, updated GROUP BY and SAMPLE BY syntax blocks, and the cairo.sql.max.grouping.sets configuration property.
1 parent 2a6d073 commit 5f30556

8 files changed

Lines changed: 367 additions & 17 deletions

File tree

documentation/configuration/configuration-utils/_cairo.config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@
419419
"default": "1",
420420
"description": "Number of partition expected on average. Initial value for purge allocation job, extended in runtime automatically."
421421
},
422+
"cairo.sql.max.grouping.sets": {
423+
"default": "4096",
424+
"description": "Maximum number of grouping sets allowed in a single query. ROLLUP produces N+1 sets, CUBE produces 2^N sets, and explicit GROUPING SETS produces one set per listed group. Queries exceeding this limit are rejected at parse time."
425+
},
422426
"cairo.sql.parallel.groupby.enabled": {
423427
"default": "true",
424428
"description": "Enables parallel GROUP BY execution; requires at least 4 shared worker threads."

documentation/cookbook/sql/time-series/fill-from-one-column.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ FROM with_previous_vals;
6969

7070
:::info Related Documentation
7171
- [SAMPLE BY](/docs/query/sql/sample-by/)
72-
- [FILL keyword](/docs/query/sql/sample-by/#fill-keywords)
72+
- [FILL keyword](/docs/query/sql/sample-by/#fill-options)
7373
- [Window functions](/docs/query/functions/window-functions/syntax/)
7474
- [last_value()](/docs/query/functions/window-functions/reference/#last_value)
7575
:::

documentation/query/sql/cube.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: CUBE keyword
3+
sidebar_label: CUBE
4+
description: CUBE SQL keyword reference for computing all combinations of aggregation levels.
5+
---
6+
7+
See [GROUPING SETS, ROLLUP, and CUBE](/docs/query/sql/grouping-sets/#cube).

documentation/query/sql/group-by.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@ is [optional](/docs/concepts/deep-dive/sql-extensions/#group-by-is-optional).
99

1010
## Syntax
1111

12-
![Flow chart showing the syntax of the GROUP BY keyword](/images/docs/diagrams/groupBy.svg)
12+
```questdb-sql
13+
SELECT column [, ...], aggregate(column) [, ...]
14+
FROM table
15+
[WHERE condition]
16+
GROUP BY
17+
column [, ...]
18+
| ROLLUP(column [, ...])
19+
| CUBE(column [, ...])
20+
| GROUPING SETS ((column [, ...]) [, ...])
21+
```
1322

1423
:::note
1524

@@ -72,6 +81,7 @@ GROUP BY a, b;
7281

7382
## See also
7483

84+
- [GROUPING SETS, ROLLUP, and CUBE](/docs/query/sql/grouping-sets/) - Compute subtotals and grand totals in a single query
7585
- [PIVOT](/docs/query/sql/pivot/) - Transform GROUP BY results from rows to columns
7686
- [SAMPLE BY](/docs/query/sql/sample-by/) - Time-series aggregation
7787
- [Aggregation functions](/docs/query/functions/aggregation/) - Available aggregate functions
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
---
2+
title: GROUPING SETS, ROLLUP, and CUBE
3+
sidebar_label: GROUPING SETS
4+
description: GROUPING SETS, ROLLUP, and CUBE SQL keyword reference for computing multiple levels of aggregation in a single query.
5+
---
6+
7+
`GROUPING SETS`, `ROLLUP`, and `CUBE` perform aggregation over multiple
8+
dimensions within a single query. This can be used, for example, to compute
9+
subtotals and grand totals alongside detail-level results, without multiple
10+
passes over the data.
11+
12+
## Syntax
13+
14+
Grouping sets can be used with both `GROUP BY` and `SAMPLE BY`.
15+
16+
With `GROUP BY`:
17+
18+
```questdb-sql
19+
SELECT column [, ...], aggregate(column) [, ...]
20+
FROM table
21+
[WHERE condition]
22+
GROUP BY
23+
column [, ...],
24+
ROLLUP(column [, ...])
25+
| CUBE(column [, ...])
26+
| GROUPING SETS ((column [, ...]) [, ...])
27+
```
28+
29+
With `SAMPLE BY`:
30+
31+
```questdb-sql
32+
SELECT [column [, ...],] aggregate(column) [, ...]
33+
FROM table
34+
[WHERE condition]
35+
SAMPLE BY n{units}
36+
[ROLLUP(column [, ...]) | CUBE(column [, ...]) | GROUPING SETS (...)]
37+
[FILL(...)]
38+
[ALIGN TO ...]
39+
```
40+
41+
## GROUPING SETS
42+
43+
`GROUPING SETS` gives explicit control over which grouping combinations to
44+
compute. Each set in the list produces its own group of aggregated rows.
45+
46+
```questdb-sql title="Explicit grouping sets" demo
47+
SELECT symbol, side, SUM(amount) AS total_amount, COUNT(*) AS trade_count
48+
FROM trades
49+
WHERE timestamp IN '$now-1m..$now'
50+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
51+
GROUP BY GROUPING SETS (
52+
(symbol, side),
53+
(symbol),
54+
()
55+
);
56+
```
57+
58+
- `(symbol, side)` groups by both columns (detail rows)
59+
- `(symbol)` groups by symbol only (subtotals per symbol, `side` is `NULL`)
60+
- `()` is the empty set, producing a single grand total row (both columns `NULL`)
61+
62+
You can specify any combination of column subsets. `ROLLUP` and `CUBE` are
63+
shorthand for common `GROUPING SETS` patterns.
64+
65+
## ROLLUP
66+
67+
`ROLLUP` generates hierarchical subtotals, progressively dropping columns from
68+
right to left. With N columns, `ROLLUP` produces N+1 grouping sets.
69+
70+
```questdb-sql title="Trade volume breakdown with ROLLUP" demo
71+
SELECT symbol, side,
72+
SUM(price * amount) AS volume,
73+
COUNT(*) AS trades
74+
FROM trades
75+
WHERE timestamp IN '$now-1m..$now'
76+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
77+
GROUP BY ROLLUP(symbol, side)
78+
ORDER BY symbol, side;
79+
```
80+
81+
This produces:
82+
83+
- Per-symbol, per-side detail rows
84+
- Per-symbol subtotals (`side` is `NULL`)
85+
- A single grand total row (both `NULL`)
86+
87+
`ROLLUP(symbol, side)` is equivalent to:
88+
89+
```questdb-sql
90+
GROUP BY GROUPING SETS (
91+
(symbol, side),
92+
(symbol),
93+
()
94+
)
95+
```
96+
97+
With three columns, `ROLLUP(a, b, c)` produces four grouping sets:
98+
99+
```questdb-sql
100+
GROUP BY GROUPING SETS (
101+
(a, b, c),
102+
(a, b),
103+
(a),
104+
()
105+
)
106+
```
107+
108+
## CUBE
109+
110+
`CUBE` generates all possible combinations of the specified columns. With N
111+
columns, `CUBE` produces 2^N grouping sets.
112+
113+
```questdb-sql title="Cross-tabulation with CUBE" demo
114+
SELECT symbol, side,
115+
SUM(amount) AS total_amount,
116+
GROUPING_ID(symbol, side) AS grp
117+
FROM trades
118+
WHERE timestamp IN '$now-1m..$now'
119+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
120+
GROUP BY CUBE(symbol, side)
121+
ORDER BY grp, symbol, side;
122+
```
123+
124+
`CUBE(symbol, side)` is equivalent to:
125+
126+
```questdb-sql
127+
GROUP BY GROUPING SETS (
128+
(symbol, side), -- both grouped
129+
(symbol), -- symbol only
130+
(side), -- side only
131+
() -- grand total
132+
)
133+
```
134+
135+
Ordering by `GROUPING_ID` groups the output by aggregation level:
136+
137+
- `grp=0`: all detail combinations
138+
- `grp=1`: per-symbol totals (side rolled up)
139+
- `grp=2`: per-side totals (symbol rolled up)
140+
- `grp=3`: grand total
141+
142+
`CUBE` is limited to 15 columns maximum (2^15 = 32,768 grouping sets).
143+
144+
## Composite syntax
145+
146+
Plain `GROUP BY` columns can be combined with `ROLLUP` or `CUBE`. The plain
147+
columns are always included in every grouping set.
148+
149+
```questdb-sql title="symbol always grouped, side rolled up" demo
150+
SELECT symbol, side, SUM(amount) AS total_amount
151+
FROM trades
152+
WHERE timestamp IN '$now-1m..$now'
153+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
154+
GROUP BY symbol, ROLLUP(side);
155+
```
156+
157+
This is equivalent to:
158+
159+
```questdb-sql
160+
GROUP BY GROUPING SETS (
161+
(symbol, side),
162+
(symbol)
163+
)
164+
```
165+
166+
There is no empty set `()` here because `symbol` is always present.
167+
168+
## GROUPING() and GROUPING_ID() functions
169+
170+
When columns are rolled up, they appear as `NULL` in the result. The data might
171+
also contain genuine `NULL` values. `GROUPING()` and `GROUPING_ID()` distinguish
172+
between the two.
173+
174+
### GROUPING(column)
175+
176+
Accepts a single column. Returns:
177+
178+
- `0` if the column is actively grouped (a `NULL` is a real data value)
179+
- `1` if the column is rolled up (the `NULL` is a placeholder)
180+
181+
```questdb-sql title="Identify rolled-up rows" demo
182+
SELECT symbol, side, SUM(amount) AS total_amount,
183+
GROUPING(symbol) AS gs,
184+
GROUPING(side) AS gsd
185+
FROM trades
186+
WHERE timestamp IN '$now-1m..$now'
187+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
188+
GROUP BY ROLLUP(symbol, side)
189+
ORDER BY gs, gsd, symbol, side;
190+
```
191+
192+
In the results:
193+
194+
| gs | gsd | Meaning |
195+
| -- | --- | ------- |
196+
| 0 | 0 | Detail row: both columns actively grouped |
197+
| 0 | 1 | Subtotal: grouped by symbol, side rolled up |
198+
| 1 | 1 | Grand total: both columns rolled up |
199+
200+
### GROUPING_ID(column1, column2, ...)
201+
202+
Accepts one or more columns. Returns an integer bitmask combining the
203+
`GROUPING()` values of all specified columns. Bit positions are assigned
204+
right-to-left: the rightmost argument occupies bit 0 (least significant bit).
205+
206+
```questdb-sql title="Bitmask for aggregation levels" demo
207+
SELECT symbol, side, SUM(amount) AS total_amount,
208+
GROUPING_ID(symbol, side) AS grp
209+
FROM trades
210+
WHERE timestamp IN '$now-1m..$now'
211+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
212+
GROUP BY CUBE(symbol, side)
213+
ORDER BY grp, symbol, side;
214+
```
215+
216+
For `GROUPING_ID(symbol, side)`, bit 1 is assigned to `symbol` and bit 0 to
217+
`side`:
218+
219+
| grp | Binary | Meaning |
220+
| --- | ------ | ------- |
221+
| 0 | 0b00 | Both columns grouped |
222+
| 1 | 0b01 | `side` rolled up |
223+
| 2 | 0b10 | `symbol` rolled up |
224+
| 3 | 0b11 | Both rolled up (grand total) |
225+
226+
Writing `GROUPING_ID(side, symbol)` would reverse the bit assignments.
227+
228+
## SAMPLE BY integration
229+
230+
Grouping sets work with QuestDB's `SAMPLE BY` clause for time-bucketed
231+
aggregation with multiple rollup levels.
232+
233+
```questdb-sql title="Hourly breakdown with ROLLUP" demo
234+
SELECT timestamp, symbol, SUM(amount) AS total_amount, AVG(price) AS avg_price
235+
FROM trades
236+
WHERE timestamp IN '$now-1d..$now'
237+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
238+
SAMPLE BY 1h ROLLUP(symbol)
239+
ORDER BY timestamp, symbol;
240+
```
241+
242+
Each time bucket contains one row per symbol plus one grand total row (where
243+
`symbol` is `NULL`). The timestamp column is never rolled up - it is always
244+
present as the time bucket key.
245+
246+
### FILL support
247+
248+
`FILL` works with grouping sets. Missing time buckets are filled per key
249+
combination - each distinct (symbol, grouping level) pair gets its own fill row.
250+
251+
```questdb-sql title="SAMPLE BY with FILL and ROLLUP" demo
252+
SELECT timestamp, symbol, SUM(amount) AS total_amount, AVG(price) AS avg_price
253+
FROM trades
254+
WHERE timestamp IN '$now-1d..$now'
255+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
256+
SAMPLE BY 1h ROLLUP(symbol) FILL(0)
257+
ORDER BY timestamp, symbol;
258+
```
259+
260+
Supported FILL modes:
261+
262+
| FILL mode | Supported |
263+
| ------------ | --------- |
264+
| `FILL(NONE)` | Yes |
265+
| `FILL(NULL)` | Yes |
266+
| `FILL(value)` | Yes |
267+
| `FILL(PREV)` | No |
268+
| `FILL(LINEAR)` | No |
269+
270+
`GROUPING()` and `GROUPING_ID()` values are preserved in fill rows. They are not
271+
replaced by the fill value.
272+
273+
```questdb-sql title="GROUPING values preserved in fill rows" demo
274+
SELECT GROUPING(symbol) AS gs, timestamp, symbol, SUM(amount) AS total_amount
275+
FROM trades
276+
WHERE timestamp IN '$now-1d..$now'
277+
AND symbol IN ('BTC-USDT', 'ETH-USDT')
278+
SAMPLE BY 1h ROLLUP(symbol) FILL(NULL)
279+
ORDER BY timestamp, gs, symbol;
280+
```
281+
282+
A fill row for a missing hour shows `gs=0` for detail-level fills and `gs=1` for
283+
grand-total-level fills, just like real data rows. Only aggregate columns get the
284+
fill value.
285+
286+
## Limitations
287+
288+
- **Expressions not allowed** in `ROLLUP`, `CUBE`, or `GROUPING SETS` - only
289+
column references are accepted. `ROLLUP(a + b)` is rejected; use a subquery or
290+
alias. Plain columns in composite syntax (`GROUP BY expr, ROLLUP(col)`) are not
291+
restricted.
292+
293+
- **No mixed qualified/unqualified references** to the same column -
294+
`ROLLUP(a, t.a)` is rejected. Use one form consistently.
295+
296+
- **Not supported with `LATEST ON`** - rejected with an error.
297+
298+
- **`FILL(PREV)` and `FILL(LINEAR)` not supported** with grouping sets.
299+
300+
- **`CUBE` limited to 15 columns** (2^15 = 32,768 grouping sets).
301+
302+
- **`GROUPING()` / `GROUPING_ID()` limited to 31 `GROUP BY` key columns** - the
303+
bitmask is int-based.
304+
305+
- **No multiple `ROLLUP`/`CUBE` in the same `GROUP BY`** -
306+
`GROUP BY ROLLUP(a), CUBE(b)` is not supported.
307+
308+
- **Maximum grouping sets per query** - controlled by the
309+
`cairo.sql.max.grouping.sets`
310+
[configuration property](/docs/configuration/overview/) (default 4096).
311+
`ROLLUP` produces N+1 sets, `CUBE` produces 2^N sets, and explicit
312+
`GROUPING SETS` produces one set per listed group. Queries exceeding this limit
313+
are rejected at parse time.
314+
315+
## See also
316+
317+
- [GROUP BY](/docs/query/sql/group-by/) - Standard grouping
318+
- [SAMPLE BY](/docs/query/sql/sample-by/) - Time-series aggregation
319+
- [PIVOT](/docs/query/sql/pivot/) - Transform GROUP BY results from rows to columns
320+
- [Aggregation functions](/docs/query/functions/aggregation/) - Available aggregate functions

documentation/query/sql/rollup.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: ROLLUP keyword
3+
sidebar_label: ROLLUP
4+
description: ROLLUP SQL keyword reference for computing hierarchical subtotals.
5+
---
6+
7+
See [GROUPING SETS, ROLLUP, and CUBE](/docs/query/sql/grouping-sets/#rollup).

0 commit comments

Comments
 (0)