Function Reference

ROWS BETWEEN

SQL construct

Sets the window frame: the rows of the partition that the window function uses to compute the value for the current row.

PostgreSQL 17.5
{ROWS | RANGE | GROUPS} BETWEEN frame_start AND frame_end

Parameters

ROWS | RANGE | GROUPSkeyword
How the bounds are counted: ROWS in rows, RANGE by the ORDER BY value, GROUPS in groups of rows with equal ORDER BY values
Values:ROWSRANGEGROUPS
frame_startkeyword
Start of the frame
Values:UNBOUNDED PRECEDINGoffset PRECEDINGCURRENT ROWoffset FOLLOWING
frame_endkeyword
End of the frame, not before its start
Values:offset PRECEDINGCURRENT ROWoffset FOLLOWINGUNBOUNDED FOLLOWING

Return value

Does not return a value: sets the rows for the window function inside OVER (...)

Examples

Sum of the last three payments

PostgreSQL 17.5
SELECT payment_id,
	date,
	unit_price,
	SUM(unit_price) OVER (
		ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
	) AS last_three
FROM Payments
ORDER BY date;
payment_iddateunit_pricelast_three
152005-02-0388
12005-02-1220002008
62005-02-201002108
162005-03-1172107
172005-03-188115
22005-03-2321002115
182005-04-2082116
192005-05-1372115
32005-05-142035
232005-06-05300327

Running total for each family member

PostgreSQL 17.5
SELECT family_member,
	date,
	unit_price,
	SUM(unit_price) OVER (
		PARTITION BY family_member
		ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
	) AS running_total
FROM Payments
ORDER BY family_member,
	date;
family_memberdateunit_pricerunning_total
12005-02-0388
12005-02-1220002008
12005-03-1172015
12005-04-2082023
12005-05-1372030
12005-06-053002330
12006-01-151000012330
12006-03-121012340
22005-03-1888
22005-03-2321002108

Sum over the last 30 days

PostgreSQL 17.5
SELECT date,
	unit_price,
	SUM(unit_price) OVER (
		ORDER BY date RANGE BETWEEN INTERVAL '30 days' PRECEDING
			AND CURRENT ROW
	) AS last_30_days
FROM Payments
ORDER BY date;
dateunit_pricelast_30_days
2005-02-0388
2005-02-1220002008
2005-02-201002108
2005-03-1172107
2005-03-188115
2005-03-2321002115
2005-04-2082108
2005-05-13715
2005-05-142035
2005-06-05300327

ROWS, RANGE and GROUPS with equal prices

PostgreSQL 17.5
SELECT unit_price,
	COUNT(*) OVER (
		ORDER BY unit_price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
	) AS rows_frame,
	COUNT(*) OVER (
		ORDER BY unit_price RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
	) AS range_frame,
	COUNT(*) OVER (
		ORDER BY unit_price GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW
	) AS groups_frame
FROM Payments
ORDER BY unit_price;
unit_pricerows_framerange_framegroups_frame
7122
7222
8355
8455
8555
10664
16772
20882
59992
10010113

Details

Bounds: UNBOUNDED PRECEDING is the start of the partition, offset PRECEDING is offset rows before the current one, CURRENT ROW is the current row, offset FOLLOWING is offset rows after it, and UNBOUNDED FOLLOWING is the end of the partition. For RANGE the offset is given in ORDER BY units, for example INTERVAL '30 days' for dates.

If no frame is given, with ORDER BY it goes from the start of the partition to the current row together with all rows that have the same ORDER BY value (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), and without ORDER BY it is the whole partition. That is why LAST_VALUE and NTH_VALUE without an explicit frame often return something unexpected.

After the frame you can exclude rows: EXCLUDE CURRENT ROW, EXCLUDE GROUP or EXCLUDE TIES. The frame does not affect ranking functions, LAG and LEAD.

See also

Where to learn