# 26000 — invalid_sql_statement_name

> PostgreSQL SQLSTATE 26000: named prepared statement lookup, session affinity, and recovery.
---

# 26000 — invalid_sql_statement_name

## At a glance {#at-a-glance}

`26000` means that PostgreSQL was asked to use a named prepared statement that does not exist in the current backend session. It is a session-resource lookup failure, so first check session affinity and statement lifecycle before changing the SQL text.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `26000` |
| Condition | `invalid_sql_statement_name` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_INVALID_SQL_STATEMENT_NAME, ERRCODE_UNDEFINED_PSTATEMENT` |
| Aliases | `ERRCODE_UNDEFINED_PSTATEMENT` |

<!-- source facts: data/errcodes/26000.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

`PREPARE` registers a named statement in one PostgreSQL session. `EXECUTE` and `DEALLOCATE` resolve that name in the same session; a pool checkout can silently move the next command to another backend. The core `prepare.c` path reports the dynamic name with `prepared statement "%s" does not exist`. The fixed extended-query path in `postgres.c` separately reports `unnamed prepared statement does not exist` when the unnamed statement is absent.

## Diagnosis {#diagnosis}

Read the driver exception's SQLSTATE and primary message, then log the backend PID or an equivalent connection identity around `PREPARE` and `EXECUTE`. On that same connection, `pg_prepared_statements` can show whether the named entry exists; querying it on a different pooled connection is not evidence about the failing session. Distinguish a deallocation, a connection replacement, and an unnamed statement path from a malformed PREPARE statement.

## Response {#response}

If the operation is inside an explicit transaction, roll back the failed block before issuing more commands. Recreate the statement on the session that will execute it, and keep the statement definition and parameter types under the same application or pool checkout. A missing statement has not itself applied the intended operation, but the application should still use its normal request identity and side-effect checks before repeating a larger workflow.

## Messages {#messages}

The fixed core templates include `prepared statement "%s" does not exist` from `prepare.c` and `unnamed prepared statement does not exist` from the extended-query path in `postgres.c`. The selected case reports `source_file = prepare.c` and `source_function = FetchPreparedStatement`; only the named template has a server-side name substitution. Do not parse either message as a stable error string; branch on SQLSTATE and structured diagnostics.

## Representative case {#case}

The runner reads this sequence from `verify/cases/26000/snippets.json` (SHA-256 `b9fdbb48371e0d9902cb9e055ececc4878c38422773a8a1571651d9952809032`) and keeps every statement on one connection. The complete executable record is [the public case export](../data/cases/26000.json), with [structured evidence](../data/evidence/26000.json).

<!-- BEGIN SQLSTATE SNIPPET: prepared_statement_recovery -->

```sql
PREPARE statement_name(integer) AS SELECT $1 + 1;
EXECUTE statement_name(1);
DEALLOCATE statement_name;
EXECUTE statement_name(1);
```
<!-- END SQLSTATE SNIPPET -->

`statement_name` is a page-level placeholder. The runner substitutes a unique identifier, deallocates it, asserts `26000` and `IDLE`, then repeats the registry's same three statements—`PREPARE`, `EXECUTE`, and `DEALLOCATE`—to repair the session. The recovery sequence is therefore: `PREPARE` the named statement again, `EXECUTE` it on that same connection, and `DEALLOCATE` it when the checkout is finished; these are the existing `prepare`, `execute`, and `deallocate` registry statements, not a second SQL definition.

The selected runner case observed `26000` on PostgreSQL 18.6 and 10.21. It deallocated a named statement, observed the dynamic missing-name diagnostic with the connection `IDLE`, then recreated and executed the statement on that same session.

## Versions {#versions}

The locked catalogue reports this condition from the early historical boundary through the current formal snapshots. The fixed 18.6 source has both named and unnamed prepared-statement paths; the runtime comparison covers one named path on 18.6 and 10.21. The message wording and source line can vary by release, while the SQLSTATE identity remains `26000` in the selected targets.

## Related {#related}

[34000 invalid cursor name](../34000/) and [25P02 failed transaction](../25p02/) are useful when a pool/session boundary or surrounding transaction is involved.

## Sources {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt) (SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`)
- [`src.prepare.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/prepare.c#L451-L454) (SHA-256 `e37fbd5f7618e5554561d9293d8c3af7cf3190c62c5c6bbceb3fe8b97be17956`)
- [`src.postgres.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L1670-L1672) (SHA-256 `9fb62275b1badf94d01ab351337b60410cd9b3ab1fe63fa9f23d6d2185a21061`)
- [`PREPARE documentation`](https://www.postgresql.org/docs/18/sql-prepare.html) · local call scan `src.calls.REL_18_6` (SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`)
