How to fix Cursor does not exist in PostgreSQL
PostgreSQL cursor error 34000 occurs when code attempts to use, fetch from, or close a cursor that has not been declared, has been closed, or exists outside the current transaction scope. Declare the cursor within the same transaction, use WITH HOLD for cross-transaction access, or ensure the cursor is still open.
Quick fix
Fixes 34000
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT * FROM users;
FETCH NEXT FROM my_cursor;
CLOSE my_cursor;
COMMIT;- Verify the cursor is declared within the same transaction
- Use WITH HOLD to preserve the cursor across transaction boundaries
- Check for implicit transaction commits in application code
- Avoid closing the cursor prematurely
BEGIN;DECLARE my_cursor CURSOR FOR SELECT * FROM users;FETCH NEXT FROM my_cursor;CLOSE my_cursor;COMMIT;Cursor does not exist
What this error means
Error 34000 indicates that PostgreSQL encountered a reference to a cursor that does not exist or is no longer accessible. In PostgreSQL, cursors are transaction-scoped objects: they only exist within the transaction that declared them and are automatically destroyed when that transaction commits or rolls back. This error occurs when code attempts to operate on a cursor that either was never declared, has already been closed, or belongs to a different transaction that has already ended. Cursors are useful for iterating over large result sets without loading the entire dataset into memory. However, they require careful management of transaction boundaries. Unlike some database systems, PostgreSQL does not persist cursors beyond transaction boundaries by default.
Typical symptoms
- FETCH command fails with "cursor does not exist" error
- CLOSE command fails when trying to close a cursor
- Cursor works in one part of the code but not after a COMMIT statement
- Application receives error when fetching rows from a cursor in a separate function call
- PL/pgSQL function throws "cursor does not exist" when referenced in a loop
- JDBC or psycopg2 applications encounter "unnamed portal" cursor errors after transaction boundaries
Common causes
- The cursor was declared in a previous transaction that has since committed or rolled back
- The application explicitly CLOSEd the cursor and then attempted to use it again
- The cursor was never declared before attempting to FETCH or CLOSE
- In multi-transaction workflows, the cursor was accessed after the declaring transaction ended without using WITH HOLD
- Connection pooling or application frameworks automatically committing transactions between cursor declaration and fetch operations
- Nested transactions or savepoints are destroying the cursor scope unexpectedly
How to fix "Cursor does not exist"
Ensure that DECLARE, FETCH, and CLOSE all occur within the same transaction block. If you need to use the cursor across multiple statements, wrap them in a transaction:
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT * FROM users;
FETCH NEXT FROM my_cursor;
CLOSE my_cursor;
COMMIT;Without BEGIN/COMMIT, each statement is auto-committed, destroying the cursor between operations.
If you need the cursor to survive transaction boundaries, declare it with WITH HOLD:
DECLARE my_cursor CURSOR WITH HOLD FOR SELECT * FROM users;
COMMIT; -- Cursor survives this commit
FETCH NEXT FROM my_cursor; -- Works in a new transaction
CLOSE my_cursor; -- Must explicitly close when doneNote: WITH HOLD stores results in a temporary area and has memory implications for very large result sets.
Many application frameworks and database libraries automatically commit transactions, which closes cursors. Verify your connection settings:
Python (psycopg2):
conn = psycopg2.connect(dsn)
conn.autocommit = False # Ensure transactions are not auto-committed
cur = conn.cursor()
cur.execute("BEGIN")
cur.execute("DECLARE my_cursor CURSOR FOR SELECT * FROM users")
cur.execute("FETCH NEXT FROM my_cursor")
conn.commit()PHP (PDO):
$pdo = new PDO("pgsql:dbname=mydb", $user, $pass);
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
$pdo->beginTransaction();
$pdo->exec("DECLARE my_cursor CURSOR FOR SELECT * FROM users");
// ... fetch from cursor
$pdo->commit();Ensure you do not CLOSE the cursor before attempting to fetch from it:
-- WRONG: Cursor is closed before fetch
DECLARE my_cursor CURSOR FOR SELECT * FROM users;
CLOSE my_cursor;
FETCH NEXT FROM my_cursor; -- Error: cursor does not exist
-- CORRECT: Fetch first, then close
DECLARE my_cursor CURSOR FOR SELECT * FROM users;
FETCH NEXT FROM my_cursor;
CLOSE my_cursor;In PL/pgSQL, cursors declared within BEGIN...END blocks must be used before the block ends:
CREATE FUNCTION fetch_users() RETURNS SETOF users AS $$
DECLARE
my_cursor CURSOR FOR SELECT * FROM users;
user_row users;
BEGIN
OPEN my_cursor;
LOOP
FETCH my_cursor INTO user_row;
EXIT WHEN NOT FOUND;
RETURN NEXT user_row;
END LOOP;
CLOSE my_cursor;
END;
$$ LANGUAGE plpgsql;Never try to use a cursor outside the function scope where it was declared.
Advanced notes
Cursor Memory Management: Cursors without WITH HOLD are more memory-efficient for long-running transactions because the database holds only the query plan and iteration state, not the entire result set. WITH HOLD cursors, by contrast, must buffer all result rows, making them unsuitable for very large queries. Always CLOSE WITH HOLD cursors explicitly to avoid session-wide cursor accumulation.
JDBC and Named Portals: Some JDBC drivers use "unnamed portal" syntax internally for cursors. If you see error "unnamed portal 1 does not exist", it indicates the same issue: a cursor/portal was auto-committed away. Set the PostgreSQL connection property autoCommit=false.
Savepoints and Nested Transactions: PostgreSQL supports savepoints within transactions. Releasing a savepoint does not destroy cursors, but rolling back to a savepoint will. If you are using savepoints, be aware that ROLLBACK TO SAVEPOINT will close any cursors declared after that savepoint was created.
Cursor Naming: Cursor names must be unique within a transaction. If you accidentally declare two cursors with the same name, the second declaration will reuse the name, invalidating the first cursor.
Frequently asked questions
- What does "34000" mean?
- Error 34000 indicates that PostgreSQL encountered a reference to a cursor that does not exist or is no longer accessible. In PostgreSQL, cursors are transaction-scoped objects: they only exist within the transaction that declared them and are automatically destroyed when that transaction commits or rolls back. This error occurs when code attempts to operate on a cursor that either was never declared, has already been closed, or belongs to a different transaction that has already ended.
- How do I fix "34000"?
- PostgreSQL cursor error 34000 occurs when code attempts to use, fetch from, or close a cursor that has not been declared, has been closed, or exists outside the current transaction scope. Declare the cursor within the same transaction, use WITH HOLD for cross-transaction access, or ensure the cursor is still open.
- What causes "34000"?
- The cursor was declared in a previous transaction that has since committed or rolled back The application explicitly CLOSEd the cursor and then attempted to use it again The cursor was never declared before attempting to FETCH or CLOSE
- Is the "34000" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐PostgreSQL Official Documentation
- ๐PostgreSQL Error Codes(Official Docs)
- ๐PostgreSQL DECLARE Documentation(Official Docs)
- ๐PostgreSQL PL/pgSQL Cursors(Official Docs)