Skip to main content
eLearner.app
Module 7 · Lesson 3 of 427/57 in the course~12 min
Module lessons (3/4)

LAG, LEAD and running totals

LAG and LEAD are two special window functions that give you access to other rows of the same partition: the previous row (LAG) or the next one (LEAD) according to the window's ORDER BY. They are the go-to tool for row-to-row comparisons (deltas, variations, intervals) and — combined with SUM/AVG/COUNT OVER — for running totals.

LAG: the previous row

SQL
LAG(colonna)            OVER (PARTITION BY p ORDER BY o)
LAG(colonna, n)         OVER (PARTITION BY p ORDER BY o)   -- n righe indietro
LAG(colonna, n, default) OVER (PARTITION BY p ORDER BY o)  -- default se non c'è

Example: for each order, "days since the previous order by the same customer":

SQL
SELECT id,
       customer_id,
       ordered_at,
       ordered_at - LAG(ordered_at) OVER (
         PARTITION BY customer_id ORDER BY ordered_at
       ) AS since_previous_order
FROM   orders
ORDER BY customer_id, ordered_at;

Each customer's first order has since_previous_order = NULL because there is no "previous" in the partition.

LEAD: the next row

The mirror of LAG. Typical: "time until the next event", "difference with the next version".

SQL
SELECT id, ordered_at,
       LEAD(ordered_at) OVER (PARTITION BY customer_id ORDER BY ordered_at)
         - ordered_at AS to_next
FROM   orders;

Running totals: aggregates with OVER

Aggregate functions (SUM, AVG, COUNT, MAX, MIN) become cumulative when combined with OVER (… ORDER BY …):

SQL
SELECT order_id,
       total,
       SUM(total) OVER (
         PARTITION BY customer_id
         ORDER BY     ordered_at
       ) AS running_total
FROM   …;

For each row, SUM computes the sum of all rows in the partition up to that point, according to the window's ORDER BY. The result is the classic "running balance".

Try it

Exercise#sql.m7.l3.e1
Attempts: 0Loading…

For each order show id, customer_id, ordered_at and a days_from_previous column which is the number of whole days (with FLOOR) between the current order and the previous one of the same customer, ordered by ordered_at. NULL if there is no previous order. Order by customer_id and ordered_at.

Loading editor…
Show hint

LAG(ordered_at) OVER (PARTITION BY customer_id ORDER BY ordered_at) returns the timestamp of the customer's previous order. Subtract to get an INTERVAL, then convert to days.

Solution available after 3 attempts

Review exercise

Exercise#sql.m7.l3.e2
Attempts: 0Loading…

For each delivered order (status = 'delivered'), show id, customer_id, ordered_at, order_total (sum of quantity * unit_price of its line items) and running_total (running total of the totals per customer, ordered by ordered_at). Use a CTE for the order_total. Order by customer_id and ordered_at.

Loading editor…
Show hint

The CTE computes the total per order; the SUM OVER produces the running total per customer over time.

Solution available after 3 attempts