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.
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.
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.
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.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL