When PostgreSQL encounters an EXECUTE, DEALLOCATE, CLOSE, or FETCH command that references a name it cannot find, it raises:
ERROR: prepared statement "my_stmt" does not exist
SQLSTATE: 26000
The condition name is invalid_sql_statement_name and it belongs to SQLSTATE class 26 — "Invalid SQL Statement Name". The error means PostgreSQL looked up the supplied name in its internal registry of prepared statements or open cursors and found nothing matching.
What This Error Means
PostgreSQL maintains a per-session, in-memory registry of named prepared statements. When you issue PREPARE my_stmt AS SELECT ..., the plan is stored under that name for the lifetime of the session (or until explicitly removed with DEALLOCATE). If you later call EXECUTE my_stmt or DEALLOCATE my_stmt and the name my_stmt is not present — because it was never created, the session ended and reconnected, or it was already deallocated — PostgreSQL raises SQLSTATE 26000.
The same class covers named cursors declared with DECLARE. A FETCH, MOVE, or CLOSE command against a cursor name that does not exist in the current transaction will produce the same SQLSTATE, though the message will read cursor "my_cursor" does not exist.
This error does not corrupt any data or leave the session in a broken state. The statement is simply rejected. Any surrounding transaction continues normally unless your application or PL/pgSQL exception handler decides otherwise.
Common Causes
Session boundary disconnect. Named prepared statements exist only for the duration of a single database session. When connection poolers (PgBouncer in transaction mode, for example) reassign connections between client requests, a prepared statement prepared on connection A may no longer exist when the next request is routed to connection B or a recycled connection A after reconnect.
DEALLOCATE or DEALLOCATE ALL called earlier. Explicit cleanup code — or a framework that calls
DEALLOCATE ALLon connection return — removes all prepared statements. Any subsequentEXECUTEagainst those names fails.Typo or case mismatch in the statement name. PostgreSQL prepared statement names are case-sensitive.
PREPARE MyStmt AS ...followed byEXECUTE mystmtwill fail.Cursor closed or transaction ended. A cursor declared without
WITH HOLDis closed automatically when its transaction ends (commit or rollback). Attempting toFETCHfrom it afterwards produces SQLSTATE 26000.PL/pgSQL dynamic execution. In PL/pgSQL, names constructed dynamically and passed to
EXECUTE(the PL/pgSQLEXECUTEstatement for dynamic SQL) are a different mechanism, but code that tries to manually call the SQL-levelEXECUTEby name can still hit this error if the preparation step was skipped or failed silently.
How to Fix invalid_sql_statement_name
Verify the prepared statement exists before executing it. Query
pg_prepared_statementsto confirm the name is present in the current session:SELECT name, statement, prepare_time FROM pg_prepared_statements WHERE name = 'my_stmt';Re-prepare after reconnect. If you use a connection pool, prepare the statement at the start of every session rather than once at application startup. Many drivers support a "prepare on first use" mode that handles this automatically.
Use PgBouncer session mode for prepared statements. If you rely heavily on server-side prepared statements, configure PgBouncer in
sessionpooling mode. Intransactionorstatementmode, named prepared statements are unreliable across requests.Check for DEALLOCATE ALL in connection cleanup code. If a middleware layer calls
DEALLOCATE ALLwhen returning a connection to the pool, ensure your prepare step runs before the first use, not once globally.Fix case and spelling of statement names. Prepared statement names must match exactly:
PREPARE "GetUser" AS SELECT * FROM users WHERE id = $1; -- Must use the exact same name, including case: EXECUTE "GetUser"(42);Declare cursors WITH HOLD if you need them across transaction boundaries. A holdable cursor survives a
COMMIT:BEGIN; DECLARE my_cursor CURSOR WITH HOLD FOR SELECT * FROM orders; COMMIT; -- Cursor is still open after COMMIT: FETCH 10 FROM my_cursor; CLOSE my_cursor;Catch the error in PL/pgSQL and re-prepare. In stored procedures that rely on dynamic prepared statements:
DO $$ BEGIN EXECUTE 'EXECUTE my_stmt(1)'; EXCEPTION WHEN invalid_sql_statement_name THEN EXECUTE 'PREPARE my_stmt AS SELECT $1::int'; EXECUTE 'EXECUTE my_stmt(1)'; END; $$;
Additional Information
- SQLSTATE class 26 contains only one condition:
invalid_sql_statement_name(26000). There are no sub-codes. - Related SQLSTATE codes in nearby classes:
34000(invalid_cursor_name) is sometimes grouped with this error conceptually, though it has its own class. - Most PostgreSQL client drivers (libpq, psycopg2, asyncpg, JDBC) expose this as a server-side error with the SQLSTATE attached. In psycopg2 it surfaces as
psycopg2.errors.InvalidSqlStatementName; in JDBC as aSQLExceptionwithgetSQLState()returning"26000". - PgBouncer's transaction pooling mode is a very common source of this error in production. Switching to named prepared statement tracking (
track_prepared_statements = yesin newer PgBouncer versions) or using client-side parameter binding avoids the problem without changing pool mode. - This error was present from early PostgreSQL versions; no specific version introduced or changed its behavior.
Frequently Asked Questions
Why does EXECUTE my_stmt work in psql but fail in my application?
psql maintains a single persistent session, so a prepared statement created interactively survives the entire psql session. Your application likely uses a connection pool that reassigns connections between requests. The prepared statement was created on one connection and the EXECUTE ran on a different one (or the same one after it was reset).
Does this error affect the current transaction? No. SQLSTATE 26000 is a statement-level error. The surrounding transaction is not automatically rolled back unless you are inside a PL/pgSQL block that does not handle the exception, which would abort the block's subtransaction.
Can I use IF EXISTS with DEALLOCATE to avoid errors?
Yes. DEALLOCATE supports an IF EXISTS option (added in PostgreSQL 9.0) that suppresses the error if the name is not found:
DEALLOCATE IF EXISTS my_stmt;
How do I list all active prepared statements in my session?
SELECT name, statement, parameter_types, prepare_time, generic_plans, custom_plans
FROM pg_prepared_statements;
This view is session-local — it only shows statements prepared in the current connection.