PostgreSQL raises ERROR: cursor "<name>" does not exist with SQLSTATE 34000 and condition name invalid_cursor_name when a FETCH, MOVE, or CLOSE command references a cursor name that is unknown in the current session. The error means PostgreSQL cannot find an open cursor by that name to operate on.
What This Error Means
SQLSTATE 34000 belongs to error class 34 — Cursor-related errors — which covers problems with cursor lifecycle operations. The condition name invalid_cursor_name is defined in the SQL standard and PostgreSQL follows it precisely.
PostgreSQL cursors are session-scoped and transaction-aware. When you declare a cursor with DECLARE, it is registered by name in the current session (or transaction, for non-WITH HOLD cursors). Any subsequent FETCH, MOVE, or CLOSE command that references a name not currently registered triggers SQLSTATE 34000. This includes names that were never declared, names that were already closed, or names declared in a different (now-ended) transaction.
For cursors declared without WITH HOLD, the cursor is automatically closed when the transaction ends — either by COMMIT, ROLLBACK, or an error that aborted the transaction. Attempting to use such a cursor after the transaction is over raises 34000. Cursors declared WITH HOLD survive transaction boundaries but are still dropped at the end of the session or when explicitly closed.
Common Causes
Referencing a cursor before declaring it. Issuing
FETCHorMOVEbefore the correspondingDECLAREstatement has executed in the current session.Using a cursor after the transaction has ended. A cursor declared without
WITH HOLDis implicitly closed when the transaction commits or rolls back. Code that fetches rows across multiple transactions without declaring the cursorWITH HOLDwill hit this error on the second transaction.Cursor already closed. Explicitly calling
CLOSE mycursorand then issuing anotherFETCH mycursorlater in the same session.Typo or case mismatch in the cursor name. Unquoted cursor names are folded to lowercase by PostgreSQL. Declaring
DECLARE MyCursor CURSOR FOR ...and then fetching from"MyCursor"(quoted, mixed case) will fail because PostgreSQL stored the name asmycursor.Cursor declared in a PL/pgSQL block that has already exited. Cursors opened inside a PL/pgSQL function or anonymous block are closed when that block exits; they cannot be referenced from outside.
Race condition in connection pooling. If a connection is returned to a pool mid-transaction and re-used, the new consumer may attempt to use a cursor name from a prior session, which no longer exists.
How to Fix invalid_cursor_name
Verify the cursor is declared before use. Ensure
DECLAREruns in the same transaction as the subsequentFETCH/MOVE/CLOSE:BEGIN; DECLARE emp_cursor CURSOR FOR SELECT id, name FROM employees ORDER BY id; FETCH 10 FROM emp_cursor; CLOSE emp_cursor; COMMIT;Use
WITH HOLDwhen fetching across transaction boundaries. If your application commits between fetches, declare the cursorWITH HOLDso it survives the commit:BEGIN; DECLARE emp_cursor CURSOR WITH HOLD FOR SELECT id, name FROM employees ORDER BY id; COMMIT; -- cursor remains open FETCH 10 FROM emp_cursor; -- works in a new implicit transaction CLOSE emp_cursor;Check for and handle already-closed cursors in PL/pgSQL. Use an exception handler if you are not certain whether a cursor is still open:
DO $$ DECLARE cur CURSOR FOR SELECT id FROM employees; BEGIN OPEN cur; FETCH cur INTO ...; CLOSE cur; -- Do not attempt to FETCH or CLOSE cur again here END; $$;Normalize cursor names to lowercase. To avoid case mismatch issues, always use lowercase unquoted names, or always double-quote the exact case you intend to use consistently:
-- Safe: unquoted, both sides lowercase DECLARE emp_cursor CURSOR FOR SELECT 1; FETCH 1 FROM emp_cursor; -- Also safe: quoted consistently DECLARE "EmpCursor" CURSOR FOR SELECT 1; FETCH 1 FROM "EmpCursor";Check
pg_cursorsto inspect open cursors. If you are unsure which cursors are open in the current session, query the system view:SELECT name, statement, is_holdable, is_scrollable FROM pg_cursors;
Additional Information
- SQLSTATE
34000is defined in the SQL standard. PostgreSQL has implemented it since version 7.4 when error codes were formalized. - Related SQLSTATE codes in the cursor class (
34): there is only one standard condition in this class —34000itself. - Adjacent cursor-related errors include
24000(invalid_cursor_state), which is raised when a cursor operation is attempted in an inappropriate state (e.g.,FETCHon a cursor that has not been opened withOPENin PL/pgSQL context). - JDBC and psycopg2/psycopg3: Both drivers surface this as a
DatabaseErrororProgrammingErrorwith the SQLSTATE34000attached. Psycopg2 raisespsycopg2.errors.InvalidCursorName. psycopg3 raisespsycopg.errors.InvalidCursorName. - ORMs: ActiveRecord, SQLAlchemy, and similar ORMs do not use named SQL cursors directly; they use server-side cursors only when explicitly requested (e.g., SQLAlchemy's
stream_results=Trueoryield_per()). The error is more common in raw SQL or stored procedure code. WITH HOLDcursors hold a snapshot of the result set after commit, which consumes memory. Close them as soon as you are done to avoid resource leaks.
Frequently Asked Questions
Why does my cursor disappear after COMMIT?
Cursors declared without WITH HOLD are scoped to the transaction in which they were declared. PostgreSQL automatically closes them when that transaction ends, whether by COMMIT or ROLLBACK. To keep a cursor open across transaction boundaries, declare it WITH HOLD.
Can I check whether a cursor is open before fetching from it?
Yes. Query the pg_cursors view, which lists all currently open cursors in the session:
SELECT count(*) FROM pg_cursors WHERE name = 'my_cursor';
In PL/pgSQL you can also use an EXCEPTION WHEN invalid_cursor_name THEN block to handle the case gracefully.
Does invalid_cursor_name abort my transaction?
Yes. Like most errors in PostgreSQL, 34000 puts the current transaction into an aborted state. You must issue ROLLBACK (or ROLLBACK TO SAVEPOINT) before you can execute further commands on that connection.
My cursor name looks correct but the error still fires — why?
The most likely cause is case folding. PostgreSQL folds unquoted identifiers to lowercase at parse time. If you declared DECLARE MyC CURSOR FOR ..., PostgreSQL stored it as myc. A subsequent FETCH MyC FROM ... also folds to myc and should work — but FETCH "MyC" FROM ... (double-quoted) looks for the name MyC exactly, which does not exist. Use consistent quoting throughout.