PL/pgSQL
Cursors
16
Copyright
© Postgres Professional, 2017–2025
Authors: Egor Rogov, Pavel Luzanov, Ilya Bashtanov, Igor Gnatyuk
Translated by: Liudmila Mantrova, Alexander Meleshko, Elena Sharafutdinova
Photo by: Oleg Bartunov (Phu monastery, Bhrikuti summit, Nepal)
Use of Course Materials
Non-commercial use of course materials (presentations, demonstrations) is
allowed without restrictions. Commercial use is possible only with the written
permission of Postgres Professional. It is prohibited to make changes to the
course materials.
Feedback
Please send your feedback, comments and suggestions to:
Disclaimer
Postgres Professional assumes no responsibility for any damages and losses,
including loss of income, caused by direct or indirect, intentional or accidental
use of course materials. Postgres Professional company specifically disclaims
any warranties on course materials. Course materials are provided “as is,” and
Postgres Professional company has no obligations to provide maintenance,
support, updates, enhancements, or modifications.
2
Topics
What are Cursors
Declaration and Opening
Operations with Cursors
Loops over Cursors and Query Results
Passing a Cursor to Clients
3
What are Cursors
A cursor implies iterative processing
a full output takes too much memory
only some part of the output is needed, but its size is unknown beforehand
allows the client to control the output size
you really need row-by-row processing (you usually don’t)
We have already learned about the concept of cursors in the Architecture.
PostgreSQL overview topic. In that topic, cursors were presented as a
server feature, and we explained how to access them using SQL. Now let’s
talk about how to use cursors in PL/pgSQL.
Why do we need cursors at all? SQL is a declarative language; first and
foremost, it is designed to work with sets of rows, which is its strength and
advantage. Being a procedural language, PL/pgSQL has to work with data
row by row, using explicit loops. This can be achieved via cursors.
For example, a full SELECT result may take up too much memory, so it has
to be processed piece by piece, or the required size of the output is
unknown beforehand, for example, the query must be stopped at some
point, or you need to let the client manage the output.
(Although row-by-row processing may be required from time to time, the
same outcome can often be achieved using pure SQL, with simpler and
more efficient code.)
4
Declaration and Opening
Unbound cursor variables
a variable of the refcursor type is declared
the actual query is specified when the cursor is being opened
Bound cursor variables
the query is specified at declaration time (possibly with parameters)
the arguments are passed when the cursor is opened
Features
the value of the cursor variable is the cursor name
(can be specified explicitly or generated automatically)
PL/pgSQL variables become implicit parameters of the query
(their arguments are filled in when the cursor is opened)
the query is prepared
Not supported in SQL
SQL uses a single DECLARE command that both declares and opens a
cursor. In PL/pgSQL, these are two different steps. Besides, there are so-
called cursor variables that are used for cursor access. These variables
have the refcursor type and virtually contain the name of the cursor (if you
do not provide this name explicitly, PL/pgSQL ensures that it is unique).
A cursor variable can be declared without being bound to a particular query.
Then you have to specify the query when you open the cursor.
Alternatively, you can specify the query (including parameters) when
declaring a variable. Then, you’ll only have to pass the arguments when
opening the cursor.
These methods are equivalent; which one to use is a matter of taste. Both
bound and unbound cursor variables are initialized only when the cursor is
being opened. In both cases, a query can have implicit parameters, which
are derived from PL/pgSQL variables.
A query opened with a cursor is prepared automatically.
6
Operations with Cursors
Fetch
row by row only
Access the current row
for simple queries only (one table, no grouping or sorting)
Processing is usually performed in a loop
a FOR loop over a cursor
a FOR loop over a query without an explicitly declared cursor
Close
explicitly or automatically at transaction end
in SQL
DECLARE
WITH HOLD
in SQL
the batch size
is configurable
PL/pgSQL allows fetching data from a cursor row by row only. It is done via
the FETCH INTO command.
If the query is simple enough (it works with one table, without grouping or
sorting), it is possible to access the current row of the cursor within
commands such as UPDATE or DELETE.
Procedural processing implies looping through data. The rows returned by a
cursor can be iterated through and processed using control commands that
are already familiar to us. But since such loops are required quite often,
PL/pgSQL offers a special flavor of the FOR command that implements
them. We have already seen FOR working with integers in the PL/pgSQL.
Overview and Programming Structures section; this one works with cursors.
Moreover, there is one more flavor of the FOR loop that does not require
cursor declaration at all: the query itself is specified within the command.
A cursor can be explicitly closed by the CLOSE command, but it will be
closed anyway once the transaction is complete (in SQL, a cursor can
remain open even after the transaction has finished if you have specified
WITH HOLD).
8
Passing a Cursor to Clients
backend
client
application
backend
PL/pgSQL function
refcursor
cursor (portal)
A PL/pgSQL cursor variable (of the refcursor type) contains the name of an
open SQL cursor. To denote the memory allocated in the backend for
keeping the cursor state, the term portal is used.
This way, a PL/pgSQL function can open a cursor and return its name to the
client. Then, the client will be able to work with the cursor as if it has been
opened by this client, but will have access only to the provided data. It adds
one more way of setting up the interface between the database and the
application.
10
Takeaways
A cursor allows fetching and processing data row by row
A FOR loop can simplify cursor handling
Processing data in loops is common for procedural languages,
but should not be overused
11
Practice
1. Modify the book_name function: if the book has more than two
authors, the title should include only the first two, while the rest
are to be replaced with “et al.”
Check that the function works fine in SQL and in the application.
2. Try rewriting the book_name function in SQL.
Which implementation do you prefer: PL/pgSQL or SQL?
1. For example:
101 Famous Poems. Alexander S. Pushkin, William Shakespeare, Ivan
A. Bunin →
→ 101 Famous Poems. Alexander S. Pushkin, William Shakespeare, et
al.
12
Practice+
1. Energy expenses must be distributed between different
departments in proportion to their headcount (the list of
departments is stored in a table).
Create a function that takes the total energy cost as an argument
and saves the distributed expenses in different table rows.
The values are rounded to cents; the sum of expenses of all
departments must exactly match the total cost.
2. Create a set returning function that simulates merge sort. The
function should take two cursor variables; both cursors are
already open and return sorted integers in non-decreasing order.
It must return a single sorted sequence of integers from both
sources.
1. We can use the following table:
CREATE TABLE depts(
id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
employees integer,
expenses numeric(10,2)
);
INSERT INTO depts(employees) VALUES (10),(10),(10);
A possible implementation:
FUNCTION distribute_expenses(amount numeric) RETURNS void;
If 100.00 is taken as an argument, the expected result is:
expenses
----------
33.33
33.34
33.33
2. A possible implementation:
FUNCTION merge(c1 refcursor, c2 refcursor) RETURNS SETOF integer;
For example, if the first cursor returns the sequence 1, 3, 5, and the second
cursor returns the sequence 2, 3, 4, the expected result is as follows:
merge
-------
1
2
3
3
4
5