PostgreSQL Refcursor PostgreSQL · Cursor · Refcursor

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 cursorRefcursor
Bound to one specific query at declaration timeNot bound to any query — query is supplied at OPEN time
Lives only within the function that declared itCan be returned from a function and used by the caller
Each cursor name is its own thingThe 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:

DECLARE cursor_var REFCURSOR;

Opening it for a specific query (note FOR takes a SELECT here):

OPEN cursor_var FOR select_query;

Or for a dynamically-built SQL string with EXECUTE:

OPEN cursor_var FOR EXECUTE sql_text [USING values];

Fetching and closing work the same as with explicit cursors:

FETCH cursor_var INTO variable_list; CLOSE cursor_var;
PL/pgSQL — Setup
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);
Example 1 — A Refcursor in an Anonymous Block

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:

PL/pgSQL — basic refcursor
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
$$;
OUTPUT
psql — basic refcursor
postgres=#DO $$ DECLARE my_cursor REFCURSOR; rec record; BEGIN OPEN my_cursor FOR SELECT ... ; ... END $$;
NOTICE: Laptop - 1200.00
NOTICE: Smartphone - 800.00
NOTICE: Tablet - 500.00
NOTICE: Monitor - 250.00
NOTICE: Printer - 150.00
NOTICE: Headphones - 100.00
NOTICE: Keyboard - 50.00
NOTICE: Mouse - 20.00
DO
Example 2 — Choosing the Query at Runtime

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:

PL/pgSQL — runtime query choice
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
$$;
OUTPUT
psql — top 3 by price
postgres=#DO $$ DECLARE my_cursor REFCURSOR; ... IF sort_by = 'price' THEN OPEN my_cursor FOR ... END IF; ... END $$;
NOTICE: Laptop - 1200.00
NOTICE: Smartphone - 800.00
NOTICE: Tablet - 500.00
DO
Example 3 — Returning a Refcursor from a Function

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:

PL/pgSQL — function returning refcursor
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:

PL/pgSQL — caller fetches
BEGIN;
SELECT get_expensive_products(500);
FETCH ALL FROM expensive_products_cur;
COMMIT;
OUTPUT
psql — function returns cursor
postgres=#BEGIN;
BEGIN
postgres=#SELECT get_expensive_products(500);
get_expensive_products
--------------------------
expensive_products_cur
(1 row)
postgres=#FETCH ALL FROM expensive_products_cur;
product_name | price
--------------+--------
Laptop | 1200.00
Smartphone | 800.00
Tablet | 500.00
(3 rows)
postgres=#COMMIT;
COMMIT
⚠️ Cursors live within a transaction. Without the explicit 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.
Example 4 — Dynamic SQL with EXECUTE

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:

PL/pgSQL — dynamic SQL refcursor
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
$$;
OUTPUT
psql — dynamic SQL
postgres=#DO $$ DECLARE my_cursor REFCURSOR; ... OPEN my_cursor FOR EXECUTE sql_text USING target_category; ... END $$;
NOTICE: Laptop - 1200.00
NOTICE: Smartphone - 800.00
NOTICE: Tablet - 500.00
NOTICE: Monitor - 250.00
NOTICE: Printer - 150.00
DO
💡 The $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 functionImplicit cursor: FOR rec IN SELECT ... LOOP
Multiple opens of same query body with different argsExplicit cursor with parameters
Return a result set from a function to a callerRefcursor — or modern RETURNS TABLE(...) / RETURN QUERY
Pass a cursor reference to another functionRefcursor — only this works
Build a query string at runtimeRefcursor with OPEN ... FOR EXECUTE
📌 Modern alternative: 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.
  1. Use RETURNS TABLE / RETURN QUERY as the default for functions that produce row results — simpler than refcursors and doesn't require the caller to manage transaction boundaries.
  2. 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.
  3. 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.
  4. Use USING for parameters in OPEN ... FOR EXECUTE — never concatenate user input into the SQL text.
  5. 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.
  6. 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 with OPEN name FOR query; (or FOR 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 ... COMMIT on the caller side — cursors live inside a transaction.
  • Use OPEN ... FOR EXECUTE ... USING for safe dynamic SQL.
  • For the common "function returning rows" case, modern RETURNS TABLE / RETURN QUERY is simpler. Refcursors shine for dynamic SQL and explicit cursor handling.