rollup
First introduced in version: 3.00.6
The ROLLUP clause / rollup function automatically generates subtotal rows and a grand total row by grouping level on top of regular GROUP BY results. It is commonly used in reports, business analysis, statistical dashboards, and other scenarios where detail rows, subtotals, and totals need to be displayed together.
The ROLLUP clause supports queries on distributed tables (OLAP, TSDB, PKEY, and IOTDB engines) with the same syntax as in-memory tables.
Syntax
DolphinDB supports the following two standard SQL forms:
Syntax 1: Function form
SELECT/EXEC group_column(s), aggregate_function(column_name)
FROM table_name
WHERE condition
GROUP BY rollup(group_column_1, group_column_2, ...);
Syntax 2: Keyword form
SELECT/EXEC group_column(s), aggregate_function(column_name)
FROM table_name
WHERE condition
GROUP BY group_column_1, group_column_2, ... WITH ROLLUP;
Where:
-
In Syntax 1,
rollup(...)must be written in lowercase. -
group_column_1, group_column_2, ...are the columns to group by; -
In the SELECT/EXEC clause, columns other than grouping columns must be enclosed in aggregate functions (such as
count,sum,avg, ormax); -
ROLLUP can be used together with common SQL clauses such as WHERE, HAVING, ORDER BY, LIMIT/TOP, JOIN, and subqueries.
Execution Semantics
The two forms have identical semantics. Based on the grouped result, they generate
aggregations level by level from right to left according to the grouping columns. For
GROUP BY rollup(c1, c2, ..., cn), the system generates aggregate
results at the following levels in sequence:
-
(c1, c2, ..., cn): the finest-grained grouping result;
-
(c1, c2, ..., c(n-1)): the first-level subtotal after removing the last grouping column;
-
Continue rolling up level by level to the left;
-
(): the total of all data.
For example:
-
GROUP BY rollup(region, product)generates detail rows at the (region, product) level, subtotals at the (region) level, and a total; -
GROUP BY rollup(year, month, day)generates four levels: (year, month, day), (year, month), (year), and the total.
In subtotal or total rows generated by ROLLUP, grouping columns that have been rolled up are displayed as NULL. Therefore, NULL values in the result may indicate either null values in the original data or subtotal/grand total rows at a certain level. Interpret the result by considering the grouping levels.
-
The ROLLUP clause / rollup function is valid only in the GROUP BY clause. It cannot be used outside of GROUP BY.
-
ROLLUP cannot be used together with DolphinDB's
groupbyfunction. -
ROLLUP generates additional subtotal/grand total rows. If subsequent programs depend on the number of result rows or need to distinguish detail rows from aggregated rows, identify these rows in the business logic.
-
NULL values in grouping columns should be interpreted with caution. NULLs in the original data and summary rows generated by ROLLUP may both appear as NULL.
-
The order of grouping columns affects the summary levels. If the column order changes, the meaning of subtotals changes accordingly.
Examples
The following examples create two tables: iot_alarm (IoT alarm data) and trades (financial trade data). All subsequent examples query these two tables.
// IoT: device alarm details
drop table if exists iot_alarm;
create table iot_alarm(
site STRING,
devType STRING,
deviceId STRING,
alarmType STRING,
ts TIMESTAMP
);
go;
insert into iot_alarm values ("A","Pump" ,"P-001","HIGH_TEMP", 2026.06.27T10:00:00);
insert into iot_alarm values ("A","Pump" ,"P-001","VIBRATION", 2026.06.27T10:05:00);
insert into iot_alarm values ("A","Pump" ,"P-002","VIBRATION", 2026.06.27T10:00:00);
insert into iot_alarm values ("A","Valve","V-009","LEAK" , 2026.06.27T10:00:00);
insert into iot_alarm values ("B","Pump" ,"P-010","HIGH_TEMP", 2026.06.27T11:00:00);
// Finance: tick-by-tick trade details
drop table if exists trades;
create table trades(
tradeDate DATE,
exch STRING,
symbol STRING,
qty LONG,
price DOUBLE
);
go;
insert into trades values (2026.06.27, "SSE" , "600000", 100, 10.00);
insert into trades values (2026.06.27, "SSE" , "600000", 200, 10.10);
insert into trades values (2026.06.27, "SSE" , "600519", 10, 1500.00);
insert into trades values (2026.06.27, "SZSE", "000001", 300, 12.00);
insert into trades values (2026.06.27, "SZSE", "000002", 100, 8.00);
Example 1. The following query counts the number of alarms for each device and automatically generates a total row.
SELECT deviceId, count(*) as alarm_cnt
FROM iot_alarm
GROUP BY rollup(deviceId)
ORDER BY deviceId
| deviceId | alarm_cnt |
|---|---|
| 5 | |
| P-001 | 2 |
| P-002 | 1 |
| P-010 | 1 |
| V-009 | 1 |
The result can be understood as two parts:
-
The number of alarms for each deviceId;
-
The total row where deviceId = NULL (the first row), indicating the total number of alarms across all devices.
Example 2. The following query counts alarms in site A by device type and device, and generates device-type subtotals and a table-level total.
SELECT devType, deviceId, count(*) as alarm_cnt
FROM iot_alarm
WHERE site = "A"
GROUP BY rollup(devType, deviceId)
ORDER BY devType, deviceId
| devType | deviceId | alarm_cnt |
|---|---|---|
| 4 | ||
| Pump | 3 | |
| Pump | P-001 | 2 |
| Pump | P-002 | 1 |
| Valve | 1 | |
| Valve | V-009 | 1 |
This query returns results at three levels:
-
(devType, deviceId): detail aggregation by device type and device;
-
(devType, NULL): device-type subtotal;
-
(NULL, NULL): total.
Example 3. The following example aggregates turnover by exchange and security symbol, and automatically generates exchange subtotals and a market-wide grand total.
SELECT exch, symbol, sum(qty * price) as turnover
FROM trades
WHERE tradeDate = 2026.06.27
GROUP BY exch, symbol WITH ROLLUP
ORDER BY exch, symbol
| exch | symbol | turnover |
|---|---|---|
| 22,420 | ||
| SSE | 18,020 | |
| SSE | 600000 | 3,020 |
| SSE | 600519 | 15,000 |
| SZSE | 4,400 | |
| SZSE | 000001 | 3,600 |
| SZSE | 000002 | 800 |
DolphinDB supports two equivalent forms:
-
GROUP BY col1, col2 WITH ROLLUP: consistent with MySQL syntax and more concise. -
GROUP BY rollup(col1, col2): consistent with PostgreSQL/SQL Server/Oracle syntax. The grouping columns are wrapped in a function, making the grouping levels easier to read.
The two forms produce the same result. Choose either form based on your preference.
Example 4. ROLLUP subtotal levels depend on the order of grouping columns. Both of the following queries are valid, but the generated subtotal levels have different meanings.
SELECT devType, deviceId, count(*) as alarm_cnt
FROM iot_alarm
WHERE site = "A"
GROUP BY rollup(devType, deviceId)
ORDER BY devType, deviceId
| devType | deviceId | alarm_cnt |
|---|---|---|
| 4 | ||
| Pump | 3 | |
| Pump | P-001 | 2 |
| Pump | P-002 | 1 |
| Valve | 1 | |
| Valve | V-009 | 1 |
SELECT deviceId, devType, count(*) as alarm_cnt
FROM iot_alarm
WHERE site = "A"
GROUP BY rollup(deviceId, devType)
ORDER BY deviceId, devType
| deviceId | devType | alarm_cnt |
|---|---|---|
| 4 | ||
| P-001 | 2 | |
| P-001 | Pump | 2 |
| P-002 | 1 | |
| P-002 | Pump | 1 |
| V-009 | 1 | |
| V-009 | Valve | 1 |
The first query generates subtotals by device type ((devType, NULL)), whereas the second generates subtotals by device ((deviceId, NULL)). Therefore, when writing SQL for reports, first determine which level to retain as the primary summary dimension, and then decide the order of the grouping columns.
Example 5. The following example first performs hierarchical aggregation by device type and device, then filters out groups with too few alarms, and finally sorts the result.
SELECT devType, deviceId, count(*) as alarm_cnt
FROM iot_alarm
WHERE site = "A"
GROUP BY devType, deviceId WITH ROLLUP
HAVING count(*) >= 2
ORDER BY devType, deviceId
| devType | deviceId | alarm_cnt |
|---|---|---|
| 4 | ||
| Pump | 3 | |
| Pump | P-001 | 2 |
This form is suitable when you need to aggregate data first and then filter for valid statistical results.
Example 6. The following example first generates hierarchical aggregation results by exchange and security symbol, then sorts the results by turnover in descending order, and uses LIMIT to return only the top 3 rows by turnover.
SELECT exch, symbol, sum(qty * price) as turnover
FROM trades
WHERE tradeDate = 2026.06.27
GROUP BY rollup(exch, symbol)
ORDER BY turnover DESC
LIMIT 3
| exch | symbol | turnover |
|---|---|---|
| 22,420 | ||
| SSE | 18,020 | |
| SSE | 600519 | 15,000 |
The same query can also be written using the TOP syntax.
SELECT TOP 3 exch, symbol, sum(qty * price) as turnover
FROM trades
WHERE tradeDate = 2026.06.27
GROUP BY rollup(exch, symbol)
ORDER BY turnover DESC
Example 7. The following example joins the alarm detail table with a device dimension table to add the device region, and then generates detail rows, subtotals, and a grand total by region and device type.
drop table if exists device_info;
create table device_info(
deviceId STRING,
region STRING
);
go;
insert into device_info values ("P-001", "North");
insert into device_info values ("P-002", "North");
insert into device_info values ("V-009", "North");
insert into device_info values ("P-010", "South");
SELECT d.region, a.devType, count(*) as alarm_cnt
FROM iot_alarm a
JOIN device_info d ON a.deviceId = d.deviceId
GROUP BY rollup(d.region, a.devType)
ORDER BY d.region, a.devType
| region | devType | alarm_cnt |
|---|---|---|
| 5 | ||
| North | 4 | |
| North | Pump | 3 |
| North | Valve | 1 |
| South | 1 | |
| South | Pump | 1 |
Example 8. The following example first calculates the trade amount for each record in a subquery, and then performs ROLLUP aggregation by exchange and security symbol in the outer query.
SELECT exch, symbol, sum(amount) as turnover
FROM (
SELECT exch, symbol, qty * price as amount
FROM trades
WHERE tradeDate = 2026.06.27
) t
GROUP BY rollup(exch, symbol)
ORDER BY exch, symbol
| exch | symbol | turnover |
|---|---|---|
| 22,420 | ||
| SSE | 18,020 | |
| SSE | 600000 | 3,020 |
| SSE | 600519 | 15,000 |
| SZSE | 4,400 | |
| SZSE | 000001 | 3,600 |
| SZSE | 000002 | 800 |
