PostgreSQL "cursor already exists" error occurs when attempting to declare or open a cursor with a name that is already in use within the same transaction or session. This commonly happens in PL/pgSQL functions called multiple times or when cursors are not properly closed. Fix by explicitly closing and deallocating cursors, or using set-based operations instead of manual cursor management.
PostgreSQL "cursor already exists" is an error that occurs when you attempt to declare a cursor with a name that already exists in the current session or transaction scope. In PostgreSQL, cursors are session-specific database objects that allow row-by-row processing within a transaction. Unlike some other databases, PostgreSQL considers a cursor "open" immediately upon declaration. You cannot declare a cursor with the same name twice in the same transaction without first closing and deallocating the previous cursor with that name. This error is particularly common in PL/pgSQL functions (stored procedures) that are called multiple times within a transaction, or in application code that executes cursor-based queries without properly cleaning up resources. Since cursors are transaction-scoped, they persist for the entire transaction until explicitly closed or the transaction ends. The error occurs because PostgreSQL maintains a namespace of active cursors within a transaction. When your code tries to DECLARE a cursor with a name that already exists in that namespace, PostgreSQL rejects the operation immediately. This is a safety mechanism to prevent name conflicts and ensure predictable cursor behavior.
First, locate the code that declares the problematic cursor. This is typically in a PL/pgSQL function or an application making direct SQL cursor calls.
-- Look for DECLARE statements in your functions:
DECLARE cursor_name CURSOR FOR SELECT ...;
-- Or in application code:
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT * FROM table_name;Check your application logs or PostgreSQL error logs to identify which cursor name is causing the conflict. The error message will include the cursor name that already exists.
Review your code to see if there is a CLOSE statement for the cursor. If the cursor is declared within a function that is called multiple times in a transaction, you must close it before the function returns.
-- Proper cursor lifecycle in PL/pgSQL:
CREATE FUNCTION process_rows() RETURNS void AS $$
DECLARE
my_cursor CURSOR FOR SELECT * FROM my_table;
row RECORD;
BEGIN
OPEN my_cursor;
LOOP
FETCH NEXT FROM my_cursor INTO row;
EXIT WHEN NOT FOUND;
-- Process row...
END LOOP;
CLOSE my_cursor; -- MUST close before function returns
END;
$$ LANGUAGE plpgsql;If CLOSE is missing, add it at the end of your cursor usage.
Ensure proper cursor cleanup by calling CLOSE to release the cursor resources, and DEALLOCATE to remove the cursor definition from the session.
-- Complete cursor lifecycle:
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT * FROM my_table;
FETCH FORWARD 10 FROM my_cursor; -- Get rows
CLOSE my_cursor; -- Release cursor resources
DEALLOCATE my_cursor; -- Remove cursor definition
COMMIT;
-- In application code with try-finally pattern:
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT * FROM my_table;
-- Process cursor...
FINALLY
CLOSE my_cursor;
DEALLOCATE my_cursor;
END;Make sure CLOSE is called before DEALLOCATE. DEALLOCATE removes the cursor definition entirely, allowing you to redeclare it with the same name if needed.
The preferred solution in PostgreSQL is to use FOR loops instead of manually managing cursors. This eliminates cursor naming conflicts entirely because PostgreSQL handles the internal cursor lifecycle automatically.
-- Instead of manual cursor management:
CREATE FUNCTION process_rows_bad() RETURNS void AS $$
DECLARE
my_cursor CURSOR FOR SELECT * FROM my_table;
row RECORD;
BEGIN
OPEN my_cursor;
LOOP
FETCH NEXT FROM my_cursor INTO row;
EXIT WHEN NOT FOUND;
-- Process row...
END LOOP;
CLOSE my_cursor;
END;
$$ LANGUAGE plpgsql;
-- Use a FOR loop instead (much cleaner):
CREATE FUNCTION process_rows_good() RETURNS void AS $$
DECLARE
row RECORD;
BEGIN
FOR row IN SELECT * FROM my_table LOOP
-- Process row...
END LOOP;
-- Cursor is automatically managed, no CLOSE or DEALLOCATE needed
END;
$$ LANGUAGE plpgsql;FOR loops are more concise, less error-prone, and PostgreSQL handles all cursor housekeeping automatically. This is the recommended pattern for iterating over query results.
If you must use manual cursor management, ensure each cursor has a unique name, especially in functions that might be called multiple times. Use dynamic cursor names based on function parameters or timestamps.
-- Instead of hardcoded names:
DECLARE my_cursor CURSOR FOR SELECT * FROM my_table; -- Bad - name collision
-- Use dynamically generated names:
CREATE FUNCTION process_rows_safe(p_table_id INT) RETURNS void AS $$
DECLARE
cursor_name TEXT := 'cursor_' || p_table_id || '_' || EXTRACT(EPOCH FROM now());
BEGIN
-- Cursor names must be hardcoded in DECLARE, not dynamic
-- So instead, use different cursor names for different paths:
IF p_table_id = 1 THEN
DECLARE cursor_1 CURSOR FOR SELECT * FROM table_1;
-- Process and close cursor_1
ELSE
DECLARE cursor_2 CURSOR FOR SELECT * FROM table_2;
-- Process and close cursor_2
END IF;
END;
$$ LANGUAGE plpgsql;Alternatively, refactor to avoid reusing the same cursor name in overlapping scopes.
Verify that your transactions are properly committed or rolled back. Cursor conflicts often occur because a transaction from a previous operation is still open, and a new operation tries to create a cursor with the same name.
-- Ensure explicit transaction boundaries:
BEGIN; -- Start transaction
-- Your cursor operations here
CLOSE my_cursor;
DEALLOCATE my_cursor;
COMMIT; -- Commit transaction, cursor is gone
-- New operation can now reuse the cursor name:
BEGIN;
DECLARE my_cursor CURSOR FOR SELECT ...; -- No conflict now
-- ...
COMMIT;If using connection pooling or an application framework, ensure each logical operation has clear transaction boundaries. Nested transactions should use SAVEPOINT instead of BEGIN.
After implementing fixes, test your functions or cursor-based code both in isolation and when called multiple times in sequence.
-- Test single invocation (will likely work):
SELECT process_rows();
-- Test multiple invocations in same transaction (this is the real test):
BEGIN;
SELECT process_rows();
SELECT process_rows(); -- This would fail if cursors not closed
SELECT process_rows(); -- And this too
COMMIT;
-- In your test suite:
-- Run the same function multiple times in a single test case
-- Verify no cursor conflicts occurMultiple invocations in a single transaction are the real-world scenario where these errors surface. Make sure your fix works in this context.
Cursor behavior in PostgreSQL is transaction-scoped. The server does not implement a separate OPEN statement—cursors are considered open immediately upon DECLARE and persist until explicitly CLOSED or the transaction ends. This differs from some other database systems where cursors might have a different lifecycle.
For application developers, consider avoiding cursors entirely in favor of set-based SQL operations. PostgreSQL is highly optimized for set-based queries and can often outperform row-by-row cursor processing. If you must use cursors for business logic reasons (e.g., maintaining state across rows), ensure rigorous resource cleanup.
In PL/pgSQL, the FOR loop construct is the idiomatic way to iterate over query results. It completely abstracts cursor management and eliminates naming conflicts. Use manual cursors only when you need advanced features like SCROLL (bidirectional iteration) or SENSITIVE cursors (see live data changes).
When working with connection pooling (PgBouncer, pgpool), be especially careful about transaction isolation modes. If you use connection pooling, ensure that transactions are properly completed (COMMIT or ROLLBACK) before the connection returns to the pool, otherwise cursor state from a previous client might conflict with a new client's cursor declarations.
For debugging cursor issues, query pg_cursors system view to see currently active cursors in your session:
SELECT * FROM pg_cursors;This shows all declared cursors and their query text, helping you identify unexpected cursors or resource leaks.
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