How to fix Cursor does not exist in PostgreSQL

PostgreSQLINTERMEDIATEMEDIUMUpdated Dec 29, 2025ยท 202 views

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;
  1. Verify the cursor is declared within the same transaction
  2. Use WITH HOLD to preserve the cursor across transaction boundaries
  3. Check for implicit transaction commits in application code
  4. Avoid closing the cursor prematurely
postgresql โ€” error
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

  1. The cursor was declared in a previous transaction that has since committed or rolled back
  2. The application explicitly CLOSEd the cursor and then attempted to use it again
  3. The cursor was never declared before attempting to FETCH or CLOSE
  4. In multi-transaction workflows, the cursor was accessed after the declaring transaction ended without using WITH HOLD
  5. Connection pooling or application frameworks automatically committing transactions between cursor declaration and fetch operations
  6. Nested transactions or savepoints are destroying the cursor scope unexpectedly

How to fix "Cursor does not exist"

1Verify the cursor is declared within the same transaction

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:

sql
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.

2Use WITH HOLD to preserve the cursor across transaction boundaries

If you need the cursor to survive transaction boundaries, declare it with WITH HOLD:

sql
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 done

Note: WITH HOLD stores results in a temporary area and has memory implications for very large result sets.

3Check for implicit transaction commits in application code

Many application frameworks and database libraries automatically commit transactions, which closes cursors. Verify your connection settings:

Python (psycopg2):

python
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):

php
$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();
4Avoid closing the cursor prematurely

Ensure you do not CLOSE the cursor before attempting to fetch from it:

sql
-- 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;
5In PL/pgSQL functions, ensure cursor is declared at function scope

In PL/pgSQL, cursors declared within BEGIN...END blocks must be used before the block ends:

plpgsql
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