PL/pgSQL Refcursor
Master PL/pgSQL refcursors — cursor variables that point to cursors. Covers OPEN ... FOR query, OPEN ... FOR EXECUTE for dynamic SQL, returning cursors from functions to callers, and when modern RETURNS TABLE / RETURN QUERY is the simpler choice. Completely rewritten from the source which had unrelated content.
A refcursor (reference cursor) is a cursor variable — a variable that points to a cursor rather than being a cursor itself. The crucial difference from a plain explicit cursor:
| Plain explicit cursor | Refcursor |
|---|---|
| Bound to one specific query at declaration time | Not bound to any query — query is supplied at OPEN time |
| Lives only within the function that declared it | Can be returned from a function and used by the caller |
| Each cursor name is its own thing | The variable can point to different cursors at different times |
Three big use cases:
- Returning a cursor from a function — caller iterates the result set themselves
- Passing a cursor to another function — the receiving function fetches from it
- Choosing the query at runtime — open different SELECTs based on input parameters
The PostgreSQL type for cursor variables is REFCURSOR — declared like any variable in the DECLARE section.
Declaring a refcursor variable:
Opening it for a specific query (note FOR takes a SELECT here):
Or for a dynamically-built SQL string with EXECUTE:
Fetching and closing work the same as with explicit cursors:
DROP TABLE IF EXISTS products;
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
price NUMERIC
);
INSERT INTO products (product_name, category, price) VALUES
('Laptop', 'Electronics', 1200.00),
('Smartphone', 'Electronics', 800.00),
('Tablet', 'Electronics', 500.00),
('Headphones', 'Audio', 100.00),
('Keyboard', 'Accessories', 50.00),
('Mouse', 'Accessories', 20.00),
('Monitor', 'Electronics', 250.00),
('Printer', 'Electronics', 150.00);
The simplest case: declare a refcursor, open it for a query, fetch rows, close it. Functionally similar to an explicit cursor, but the query is bound at OPEN time, not DECLARE time:
DO $$
DECLARE
my_cursor REFCURSOR;
rec record;
BEGIN
-- The query is supplied here, not on the DECLARE line
OPEN my_cursor FOR
SELECT product_name, price FROM products ORDER BY price DESC;
LOOP
FETCH my_cursor INTO rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE '% - %', rec.product_name, rec.price;
END LOOP;
CLOSE my_cursor;
END
$$;
The same refcursor variable can be opened for different queries depending on input. This is what plain explicit cursors can't do — they're bound to one query at declaration:
DO $$
DECLARE
my_cursor REFCURSOR;
rec record;
sort_by TEXT := 'price'; -- could come from a function parameter
BEGIN
IF sort_by = 'price' THEN
OPEN my_cursor FOR
SELECT product_name, price FROM products ORDER BY price DESC LIMIT 3;
ELSE
OPEN my_cursor FOR
SELECT product_name, price FROM products ORDER BY product_name LIMIT 3;
END IF;
LOOP
FETCH my_cursor INTO rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE '% - %', rec.product_name, rec.price;
END LOOP;
CLOSE my_cursor;
END
$$;
The classic pattern: a function builds and opens a cursor, then returns the refcursor handle. The caller fetches from it. This is how PostgreSQL functions traditionally returned multi-row result sets to client applications before RETURNS TABLE existed.
The function:
DROP FUNCTION IF EXISTS get_expensive_products(NUMERIC);
CREATE OR REPLACE FUNCTION get_expensive_products(min_price NUMERIC)
RETURNS REFCURSOR AS $$
DECLARE
result_cursor REFCURSOR := 'expensive_products_cur'; -- name is optional
BEGIN
OPEN result_cursor FOR
SELECT product_name, price FROM products
WHERE price >= min_price
ORDER BY price DESC;
RETURN result_cursor;
END;
$$ LANGUAGE plpgsql;
The caller fetches from the returned cursor. Refcursors must be used inside a transaction — wrap the calls in BEGIN ... COMMIT or run them in a transaction block:
BEGIN;
SELECT get_expensive_products(500);
FETCH ALL FROM expensive_products_cur;
COMMIT;
BEGIN ... COMMIT, each SELECT statement in psql runs in its own auto-commit transaction — and the cursor closes when that transaction ends. Wrap the function call and the FETCH in one transaction block.Refcursors paired with OPEN ... FOR EXECUTE let you build the query string dynamically — useful for reporting tools where the table or columns aren't known until runtime. Use USING for parameter values to avoid SQL injection:
DO $$
DECLARE
my_cursor REFCURSOR;
rec record;
target_category TEXT := 'Electronics';
sql_text TEXT;
BEGIN
sql_text := 'SELECT product_name, price FROM products
WHERE category = $1 ORDER BY price DESC';
OPEN my_cursor FOR EXECUTE sql_text USING target_category;
LOOP
FETCH my_cursor INTO rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE '% - %', rec.product_name, rec.price;
END LOOP;
CLOSE my_cursor;
END
$$;
$1 + USING pattern is the safe way to handle user-supplied values in dynamic SQL. Don't string-concatenate user input into the SQL text — that's a SQL injection risk. USING sends the values separately from the parsed SQL, so they can never be interpreted as code.| Want… | Use this |
|---|---|
| Iterate rows inside one function | Implicit cursor: FOR rec IN SELECT ... LOOP |
| Multiple opens of same query body with different args | Explicit cursor with parameters |
| Return a result set from a function to a caller | Refcursor — or modern RETURNS TABLE(...) / RETURN QUERY |
| Pass a cursor reference to another function | Refcursor — only this works |
| Build a query string at runtime | Refcursor with OPEN ... FOR EXECUTE |
RETURNS TABLE with RETURN QUERY. For most "function that returns rows" use cases, the modern PostgreSQL idiom is RETURNS TABLE(col1 type, col2 type) ... RETURN QUERY SELECT ... — simpler than refcursors, no transaction-boundary concerns. Refcursors remain useful for dynamic SQL, cursor-passing between functions, and compatibility with older PostgreSQL drivers that expect them.- Use
RETURNS TABLE/RETURN QUERYas the default for functions that produce row results — simpler than refcursors and doesn't require the caller to manage transaction boundaries. - Reach for refcursors when you need dynamic queries, when callers explicitly want a cursor handle to fetch from at their own pace, or when integrating with code that expects them.
- Wrap caller-side fetching in a transaction.
BEGIN ... CALL/SELECT function ... FETCH ... COMMIT. Outside a transaction, the cursor disappears when the function call's auto-commit ends. - Use
USINGfor parameters inOPEN ... FOR EXECUTE— never concatenate user input into the SQL text. - Optionally name your refcursor by assigning a string to it before
OPEN:cur := 'my_cursor_name'; OPEN cur FOR ...;— gives the caller a known handle. - CLOSE explicitly when done. The cursor will close at transaction end anyway, but clean code closes its own resources.
- A refcursor is a cursor variable — a value that points to a cursor, not a cursor bound to one query.
- Declare with
name REFCURSOR;, open withOPEN name FOR query;(orFOR EXECUTE sql_text USING values), fetch and close like any cursor. - Refcursors enable two things plain explicit cursors can't: returning a cursor from a function, and opening a different query depending on runtime conditions.
- Pair refcursor-returning functions with
BEGIN ... COMMITon the caller side — cursors live inside a transaction. - Use
OPEN ... FOR EXECUTE ... USINGfor safe dynamic SQL. - For the common "function returning rows" case, modern
RETURNS TABLE/RETURN QUERYis simpler. Refcursors shine for dynamic SQL and explicit cursor handling.