Reporting Dashboard

Aggregation, window functions, ROLLUP, and views

Tutorial 2 of 5


This tutorial builds a sales pipeline and slices it with aggregation, window functions, ROLLUP/CUBE for subtotals, and reusable views. You will rank reps, compute running totals, and generate summary rows—all in pure SQL.

1. Schema and seed data

CREATE TABLE reps (
    id    SERIAL PRIMARY KEY,
    name  TEXT NOT NULL,
    region TEXT NOT NULL
);

CREATE TABLE deals (
    id       SERIAL PRIMARY KEY,
    rep_id   INT NOT NULL REFERENCES reps(id),
    customer TEXT NOT NULL,
    amount   INT NOT NULL,
    stage    TEXT NOT NULL DEFAULT 'prospect',
    closed   DATE
);

INSERT INTO reps (name, region) VALUES
    ('Alice',  'West'),
    ('Bob',    'East'),
    ('Carol',  'West'),
    ('Dave',   'East'),
    ('Eve',    'Central');

INSERT INTO deals (rep_id, customer, amount, stage, closed) VALUES
    (1, 'Acme Corp',       45000,  'won',      '2025-01-10'),
    (1, 'Globex',          30000,  'won',      '2025-01-18'),
    (1, 'Initech',         20000,  'prospect',  NULL),
    (2, 'Umbrella',        55000,  'won',      '2025-01-05'),
    (2, 'Stark Ind',       40000,  'lost',      NULL),
    (3, 'Wayne Ent',       60000,  'won',      '2025-01-22'),
    (3, 'Oscorp',          35000,  'prospect',  NULL),
    (4, 'LexCorp',         25000,  'won',      '2025-01-15'),
    (4, 'Cyberdyne',       70000,  'won',      '2025-01-28'),
    (5, 'Aperture',        50000,  'won',      '2025-01-12'),
    (5, 'Weyland-Yutani',  15000,  'lost',      NULL);

2. Revenue by rep

Total closed revenue per rep, filtering to won deals only.

SELECT r.name,
       COUNT(*) AS deals_won,
       SUM(d.amount) AS revenue
FROM deals d
JOIN reps r ON d.rep_id = r.id
WHERE d.stage = 'won'
GROUP BY r.name
ORDER BY revenue DESC;

Result

namedeals_wonrevenue
Dave295000
Alice275000
Carol160000
Bob155000
Eve150000

3. Filter with HAVING

Only show reps with total pipeline (all stages) above 60k.

SELECT r.name,
       SUM(d.amount) AS pipeline
FROM deals d
JOIN reps r ON d.rep_id = r.id
GROUP BY r.name
HAVING SUM(d.amount) > 60000
ORDER BY pipeline DESC;

Result

namepipeline
Carol95000
Dave95000
Alice95000
Bob95000
Eve65000

4. Rank reps by revenue

Use RANK() to rank reps within each region by closed revenue.

SELECT r.name, r.region,
       SUM(d.amount) AS revenue,
       RANK() OVER (
         PARTITION BY r.region
         ORDER BY SUM(d.amount) DESC
       ) AS rnk
FROM deals d
JOIN reps r ON d.rep_id = r.id
WHERE d.stage = 'won'
GROUP BY r.name, r.region;

Result

nameregionrevenuernk
EveCentral500001
DaveEast950001
BobEast550002
AliceWest750001
CarolWest600002

5. Running revenue total

Compute a running total of closed deals ordered by close date, using a window frame.

SELECT d.closed,
       r.name,
       d.amount,
       SUM(d.amount) OVER (
         ORDER BY d.closed
         ROWS BETWEEN UNBOUNDED
           PRECEDING AND CURRENT ROW
       ) AS running_total
FROM deals d
JOIN reps r ON d.rep_id = r.id
WHERE d.stage = 'won'
ORDER BY d.closed;

Result

closednameamountrunning_total
2025-01-05Bob5500055000
2025-01-10Alice45000100000
2025-01-12Eve50000150000
2025-01-15Dave25000175000
2025-01-18Alice30000205000
2025-01-22Carol60000265000
2025-01-28Dave70000335000

6. Subtotals with ROLLUP

GROUP BY ROLLUP generates subtotal rows automatically—one per region, plus a grand total.

SELECT COALESCE(r.region, 'TOTAL')
         AS region,
       COALESCE(r.name, 'Subtotal')
         AS rep,
       SUM(d.amount) AS revenue
FROM deals d
JOIN reps r ON d.rep_id = r.id
WHERE d.stage = 'won'
GROUP BY ROLLUP(r.region, r.name)
ORDER BY r.region, r.name;

Result

regionreprevenue
CentralEve50000
CentralSubtotal50000
EastBob55000
EastDave95000
EastSubtotal150000
WestAlice75000
WestCarol60000
WestSubtotal135000
TOTALSubtotal335000

7. Wrap it in a view

Create a reusable view for the pipeline summary so any dashboard query can reference it directly.

CREATE VIEW pipeline_summary AS
SELECT r.name AS rep,
       r.region,
       COUNT(*) FILTER (
         WHERE d.stage = 'won'
       ) AS won,
       SUM(d.amount) AS pipeline,
       SUM(CASE WHEN d.stage = 'won'
           THEN d.amount ELSE 0
       END) AS revenue
FROM deals d
JOIN reps r ON d.rep_id = r.id
GROUP BY r.name, r.region;

SELECT * FROM pipeline_summary
ORDER BY revenue DESC;

Result

repregionwonpipelinerevenue
DaveEast29500095000
AliceWest29500075000
CarolWest19500060000
BobEast19500055000
EveCentral16500050000

What you learned