# 42P01 — undefined_table

> PostgreSQL reports SQLSTATE 42P01 when a referenced relation cannot be resolved. Check schema qualification, search_path, quoting, and migrations before creating or renaming objects.
---

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

`42P01` is PostgreSQL's `undefined_table` condition in Class 42, `syntax_error_or_access_rule_violation`. The name is historical: the failing reference can be a table, view, materialized view, foreign table, or another relation name that the parser cannot resolve in the current namespace.

The ordinary diagnostic is `relation "%s" does not exist` for an unqualified reference or `relation "%s.%s" does not exist` for a qualified reference. The parser reports the SQLSTATE before execution of the query; in an autocommit connection the error does not leave a failed transaction behind.

The representative case queries a schema-qualified relation that does not exist, then creates a valid relation and reads it. PostgreSQL 18.6 and the isolated PostgreSQL 10.21 target both returned `42P01`, kept the connection `IDLE`, and accepted the corrected query. The run IDs and assertions are retained in the [public evidence JSON](../data/evidence/42p01.json).

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

| Field | Value |
| --- | --- |
| SQLSTATE | `42P01` |
| Condition | `undefined_table` |
| 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_UNDEFINED_TABLE` |
| Aliases | `—` |

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

## Meaning and trigger paths {#meaning}

During parse analysis PostgreSQL resolves each relation through the active namespace and `search_path`. If a qualified name is absent, `parse_relation.c` emits the two-part message; if an unqualified name is absent, it emits the one-part form. A misspelled name, a migration that has not run, a wrong database, a changed `search_path`, or a quoted identifier with different case can all lead to this same SQLSTATE.

This is a name-resolution error, not proof that a physical table was deleted. A relation can exist in another schema or database, and a role can lack the privileges needed to see or use it. Conversely, creating a table with a guessed name can conceal a deployment or quoting error.

The parser also uses `42P01` for some invalid references to a `FROM` item and for a forward reference to a common-table expression, with different detail or hint text. Use the complete message and context to distinguish those forms from a missing relation.

## Messages and diagnostics {#messages}

The runner's operation is a schema-qualified lookup followed by a corrected relation. The harness supplies the disposable schema name; the query shape is:

<!-- BEGIN SQLSTATE SNIPPET: missing_relation -->
```sql
SELECT * FROM does_not_exist;
CREATE TABLE exists(id integer PRIMARY KEY);
SELECT count(*) FROM exists;
```
<!-- END SQLSTATE SNIPPET -->

PostgreSQL 18.6 returned:

```text
SQLSTATE: 42P01
severity: ERROR
message_primary: relation "c42p01_missing_relation.does_not_exist" does not exist
source: parse_relation.c / parserOpenTable / line 1480
```

The PG10 target used the same primary message with source line 1159. The qualified form preserves both schema and relation in the message. For an unqualified reference, the fixed source template is `relation "%s" does not exist`; a future CTE or invalid `FROM` reference can add detail or a hint.

## Diagnosis {#diagnosis}

Record SQLSTATE, primary message, detail, hint, statement position, current database, role, and `search_path`. Check the exact spelling and quoting used by the application. Query `pg_class`/`pg_namespace` or `to_regclass()` through an administrative or appropriately privileged connection to determine where the relation exists.

Compare the deployed migration revision with the connection's database and schema. A pooled connection can carry a different `search_path` from the session used during setup. If the object is intended to be temporary or session-local, confirm that the query runs in the same session that created it.

The representative error was autocommit and left the connection `IDLE`; the corrected query returned count `0` and remained `IDLE`. An explicit transaction can still become `INERROR` if the missing relation is referenced inside that transaction, so record status rather than assuming that all `42P01` cases are harmless to the surrounding work.

## Response and repair {#response}

Repair the name-resolution cause in deployment or application configuration:

- Select the intended schema explicitly or set and verify `search_path` for the session.
- Apply the missing migration in the correct database before serving queries.
- Preserve case-sensitive identifiers with exact double quoting, or rename them to a consistent convention after checking dependents.
- Use `to_regclass()` or an equivalent preflight only when a missing object is an expected branch; do not silently create a replacement relation for an unexpected deployment failure.

After correcting the relation, run the original query again and verify its result and transaction status. A successful `CREATE TABLE` or a new connection alone does not prove that every application session resolves the same object.

## Versions and boundaries {#versions}

The catalogue has a definition-presence observation for `42P01` at PostgreSQL 7.4 and through the locked 8.4.22 pre-9.0 definitions, then in every listed formal snapshot through PostgreSQL 18.6 and the PostgreSQL 19 Beta 3 preview. This is a definition-only presence boundary, not an exact implementation introduction or runtime-use claim. No condition definition change is recorded in the scanned range.

The missing-relation case passed on PostgreSQL 18.6 and 10.21. Source line numbers and parser hints differ by release and reference form. This page covers a schema-qualified absent relation and a valid follow-up query, not every namespace or access-rule path that can use `42P01`.

## Related {#related}

[`42P02` — `undefined_parameter`](../42p02/) covers a missing query parameter. [`3F000` — `invalid_schema_name`](../3f000/) covers an invalid schema name. [`42501` — `insufficient_privilege`](../42501/) is an access failure after name resolution. [`57014` — `query_canceled`](../57014/) can interrupt a corrective query but has a different cause.

## Sources {#sources}

Structured evidence is recorded in the [public evidence JSON](../data/evidence/42p01.json). Source records are fixed to PostgreSQL commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; runtime records retain both target IDs and structured observations.

- `src.errcodes.18.6` — [`errcodes.txt`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt#L371-L376)
- `src.parse-relation.18.6` — [`parse_relation.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/parser/parse_relation.c#L1473-L1501)
- `src.namespace.18.6` — [`namespace.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/catalog/namespace.c#L626-L640)
- `doc.ddl.18.6` — [Schemas](https://www.postgresql.org/docs/18/ddl-schemas.html)
- Runtime: `42P01-registry-final-20260909` on latest and pg10; structured observations are in the [public evidence JSON](../data/evidence/42p01.json)
