Open Tech for Smart Manufacturing

Insights, tutorials, and open-source tools for the factories of the future

Thursday, August 20, 2026

PostgreSQL Advanced Aggregations: GROUPING SETS, ROLLUP, CUBE, and Multi-Dimensional KPI Analysis

Go beyond GROUP BY with PostgreSQL advanced aggregations. 

PostgreSQL Advanced Aggregations: GROUPING SETS, ROLLUP, CUBE, and Multi-Dimensional KPI Analysis


In this article, we use GROUPING SETS, ROLLUP, CUBE, conditional aggregation and FILTER to turn the Smart Factory database into multi-dimensional management reports.

This time, the examples are not illustrative. The result tables below were calculated directly from the supplied Smart Factory PostgreSQL dump. The dataset contains 1,143 production orders, 17,167 production events, 82 downtime events, 8 maintenance events, 1,143 quality inspections and 2,172 energy measurements.

The objective: learn the SQL technique, see the real result, and interpret what the result means for a factory.

1. Why simple GROUP BY is not enough

A production manager may want production by line, by product, by line and product, and for the entire factory. Writing separate queries for every level quickly becomes repetitive.

SELECT
    pl.line_code,
    pl.line_name,
    SUM(po.actual_quantity) AS actual_quantity
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
GROUP BY
    pl.line_code,
    pl.line_name
ORDER BY actual_quantity DESC;

2. GROUPING SETS: several aggregation levels in one query

GROUPING SETS lets us explicitly choose the aggregation levels we want.

SELECT
    pl.line_code,
    p.product_code,
    SUM(po.actual_quantity) AS actual_quantity
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
JOIN smart_factory.products p
    ON p.product_id = po.product_id
GROUP BY GROUPING SETS (
    (pl.line_code, p.product_code),
    (pl.line_code),
    ()
);

Before looking at the complete result, start with the real line-level totals:

LineOrdersPlannedActualRejectedFulfillmentReject rate
L01381129,495126,2771,36597.51%1.08%
L02381127,560121,7592,80795.45%2.31%
L03381128,683124,9731,29597.12%1.04%

Interpretation: L02 is clearly the weak line: 95.45% fulfillment versus 97.51% for L01, while its reject rate is 2.31%—more than twice L01's 1.08%.

3. The real line + product result

Now look at the 15 line/product combinations generated by the same aggregation logic:

LineProductOrdersPlannedActualRejectedFulfillmentReject rate
L01P10017925,96025,34128597.62%1.12%
L01P10028430,05129,32832497.59%1.10%
L01P20015919,44018,98421397.65%1.12%
L01P20028328,62027,91028397.52%1.01%
L01P30017625,42424,71426097.21%1.05%
L02P10017526,62625,45855795.61%2.19%
L02P10027324,43023,28253195.30%2.28%
L02P20017223,35222,16655894.92%2.52%
L02P20028727,89426,53266295.12%2.50%
L02P30017425,25824,32149996.29%2.05%
L03P10017323,48922,79423697.04%1.04%
L03P10028629,02128,22530697.26%1.08%
L03P20017324,61423,82225996.78%1.09%
L03P20026121,67921,06718497.18%0.87%
L03P30018829,88029,06531097.27%1.07%

The pattern is operationally interesting. The weakest combination is L02 + P2001 with 94.92% fulfillment and a 2.52% reject rate. L02 + P2002 is similarly problematic at 95.12% fulfillment and 2.50% rejects.

4. Product-level subtotals

The same concept can produce a product subtotal:

GROUP BY GROUPING SETS (
    (pl.line_code, p.product_code),
    (pl.line_code),
    (p.product_code),
    ()
);
ProductOrdersPlannedActualRejectedFulfillmentReject rate
P100122776,07573,5931,07896.74%1.46%
P100224383,50280,8351,16196.81%1.44%
P200120467,40664,9721,03096.39%1.59%
P200223178,19375,5091,12996.57%1.50%
P300123880,56278,1001,06996.94%1.37%

P3001 has the best fulfillment rate at 96.94%, while P2001 has the lowest at 96.39% and the highest product-level reject rate at 1.59%.

5. The factory grand total

The empty grouping set () represents the complete factory total.

ScopeOrdersPlannedActualRejectedFulfillmentReject rate
FACTORY TOTAL1,143385,738373,0095,46796.70%1.47%

Across the complete dataset, the factory planned 385,738 units and produced 373,009, giving a fulfillment rate of 96.70%. Total rejected quantity is 5,467 units.

6. GROUPING(): distinguish subtotal rows from data

SELECT
    pl.line_code,
    p.product_code,
    SUM(po.actual_quantity) AS actual_quantity,
    GROUPING(pl.line_code) AS line_grouped,
    GROUPING(p.product_code) AS product_grouped
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
JOIN smart_factory.products p
    ON p.product_id = po.product_id
GROUP BY GROUPING SETS (
    (pl.line_code, p.product_code),
    (pl.line_code),
    ()
);

This is important when a result is consumed by an application: a NULL product in a subtotal row should not be confused with a genuinely missing product value.

7. ROLLUP: hierarchical reporting

When the hierarchy is natural, ROLLUP is shorter:

SELECT
    pl.line_code,
    p.product_code,
    SUM(po.actual_quantity) AS actual_quantity
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
JOIN smart_factory.products p
    ON p.product_id = po.product_id
GROUP BY ROLLUP (
    pl.line_code,
    p.product_code
);

Conceptually:

Line + Product
      ↓
Line subtotal
      ↓
Factory total

For this dataset, the line subtotals above are the key ROLLUP result to inspect. They reveal that all three lines processed exactly 381 orders, but L02 converted those orders into materially lower output and higher rejects.

8. CUBE: multidimensional analysis

SELECT
    pl.line_code,
    p.product_code,
    SUM(po.actual_quantity) AS actual_quantity
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
JOIN smart_factory.products p
    ON p.product_id = po.product_id
GROUP BY CUBE (
    pl.line_code,
    p.product_code
);

CUBE produces the line + product combinations, line subtotals, product subtotals and factory total. The real subtotals are shown above, so the important lesson is the structure: one query can support several analytical perspectives.

9. Conditional aggregation with FILTER()

The production orders also have operational statuses. The real dataset contains:

StatusOrders
COMPLETED1,106
CANCELLED19
IN_PROGRESS18

That means 1,106 of 1,143 orders are COMPLETED, while 19 are CANCELLED and 18 are IN_PROGRESS.

SELECT
    pl.line_code,
    COUNT(*) AS total_orders,
    COUNT(*) FILTER (
        WHERE po.status = 'COMPLETED'
    ) AS completed_orders,
    COUNT(*) FILTER (
        WHERE po.status <> 'COMPLETED'
    ) AS non_completed_orders
FROM smart_factory.production_orders po
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
GROUP BY pl.line_code;

FILTER is especially convenient when several conditional KPIs must appear beside one another.

10. Quality: the same aggregation idea

The quality inspections are linked to production orders, products and production lines. Calculate defect rate at line/product level:

SELECT
    pl.line_code,
    p.product_code,
    SUM(qi.inspected_quantity) AS inspected_quantity,
    SUM(qi.passed_quantity) AS passed_quantity,
    SUM(qi.failed_quantity) AS failed_quantity,
    ROUND(
        100.0 * SUM(qi.failed_quantity)
        / NULLIF(SUM(qi.inspected_quantity), 0),
        2
    ) AS defect_rate_pct
FROM smart_factory.quality_inspections qi
JOIN smart_factory.production_orders po
    ON po.order_id = qi.order_id
JOIN smart_factory.production_lines pl
    ON pl.line_id = po.line_id
JOIN smart_factory.products p
    ON p.product_id = po.product_id
GROUP BY pl.line_code, p.product_code;

Here are the real results:

LineProductInspectedPassedFailedDefect rate
L01P10016,3206,2111091.72%
L01P10026,7206,5991211.80%
L01P20014,7204,634861.82%
L01P20026,6406,5191211.82%
L01P30016,0805,9761041.71%
L02P10016,0005,7752253.75%
L02P10025,8405,5802604.45%
L02P20015,7605,5372233.87%
L02P20026,9606,7052553.66%
L02P30015,9205,6852353.97%
L03P10015,8405,7361041.78%
L03P10026,8806,7441361.98%
L03P20015,8405,7291111.90%
L03P20024,8804,7791012.07%
L03P30017,0406,8941462.07%

The quality signal reinforces the production signal: L02 has materially higher defect rates. Its worst combination is L02 + P1002 at 4.45%.

11. Energy: machine and line aggregation

The energy table contains daily machine measurements. We can aggregate it with ROLLUP or ordinary grouping.

SELECT
    pl.line_code,
    m.machine_code,
    SUM(ec.energy_kwh) AS total_energy_kwh,
    AVG(ec.power_kw) AS avg_power_kw
FROM smart_factory.energy_consumption ec
JOIN smart_factory.machines m
    ON m.machine_id = ec.machine_id
JOIN smart_factory.production_lines pl
    ON pl.line_id = m.line_id
GROUP BY
    pl.line_code,
    m.machine_code;
LineMachineEnergy kWhAvg power kW
L01M00117,288.9910.90
L01M00220,192.5412.89
L01M00311,198.287.04
L02M00441,762.0626.27
L02M00548,470.4030.81
L02M00639,496.7224.70
L02M00732,657.3320.63
L03M00818,695.9711.89
L03M00914,031.878.80
L03M0109,265.355.83
L03M01126,083.3516.64
L03M01214,654.039.35

12. Energy per produced unit

A raw energy total is not enough. A more useful manufacturing KPI is energy intensity:

energy_kwh / NULLIF(actual_quantity, 0)
    AS kwh_per_unit
LineEnergy kWhActual unitskWh / unit
L0148,679.81126,2770.3855
L02162,386.51121,7591.3337
L0382,730.58124,9730.6620

The difference is striking: L02 consumes about 1.3337 kWh per produced unit, compared with 0.3855 for L01 and 0.6620 for L03. This is an important example of why industrial analysis should normalize resource consumption by output.

13. What the real data tells us

There is a coherent industrial signal in the dataset.

L02 is the weakest production line: 95.45% fulfillment and 2.31% reject rate. Its quality results are also worse, and its energy intensity is much higher at 1.3337 kWh/unit. This is exactly the type of multi-dimensional pattern that advanced SQL is designed to expose.

The dataset documentation deliberately describes a discoverable industrial story in which a machine on Line 2 progressively deteriorates and affects downtime, production, quality and energy before maintenance intervention. The aggregated results above therefore give us a starting point for the next investigation: which machine on L02 is responsible?

14. GROUPING SETS vs ROLLUP vs CUBE

TechniqueBest useQuestion answered
GROUPING SETSExplicit aggregation levelsWhich exact summaries do I need?
ROLLUPHierarchical totalsHow do detail → subtotal → total relate?
CUBEMultidimensional analysisWhat happens across every dimension combination?
FILTER()Conditional KPIsHow many rows meet each condition?

15. Combining the techniques

The advanced SQL workflow is now becoming clear:

Raw industrial data
        ↓
      JOIN
        ↓
       CTE
        ↓
GROUPING SETS / ROLLUP / CUBE
        ↓
Window functions
        ↓
Industrial KPI
        ↓
Dashboard / investigation

The database already includes analytical views such as v_machine_monthly_performance, showing that this model naturally leads toward reusable analytical layers.

16. Your Smart Factory SQL challenge

  1. Reproduce the line/product production table.
  2. Add line subtotals and the factory total using GROUPING SETS.
  3. Rebuild the same hierarchy using ROLLUP.
  4. Use CUBE to generate line and product perspectives.
  5. Add GROUPING() flags.
  6. Calculate completed and non-completed orders with FILTER().
  7. Calculate quality defect rate by line/product.
  8. Calculate energy consumption by machine.
  9. Calculate kWh per produced unit by line.
  10. Finally, investigate L02 and identify the machine-level cause.

17. What you learned

  • GROUPING SETS for custom multi-level aggregation.
  • ROLLUP for hierarchical subtotals.
  • CUBE for multidimensional aggregation.
  • GROUPING() for identifying subtotal and total rows.
  • FILTER() for conditional aggregates.
  • How to interpret real production, quality and energy results.
  • How advanced aggregation can reveal an industrial signal that deserves deeper investigation.

Practice with the complete Smart Factory dataset

The complete PostgreSQL dataset contains the interconnected manufacturing environment used throughout this SQL series: production orders, machines, production events, downtime, maintenance, quality and energy data.

Smart Factory Manufacturing Dataset v0.1 — $9

→ GET THE COMPLETE DATASET ON GUMROAD

Smart Factory SQL Series — Article 12
PostgreSQL • Advanced Aggregations • GROUPING SETS • ROLLUP • CUBE • Industrial Analytics • Industry 4.0

No comments:

Post a Comment

Post Top Ad

Your Ad Spot

Pages