Skip to content
PostgreSQL

PostgreSQL Error 42P01

relation does not exist

Applies to: PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last technically reviewed 24 October 2026 on PostgreSQL 18.6 (PGlite 0.5.11)
The full message
ERROR:  relation "customers" does not exist
Other forms of this message
Schema-qualified name
ERROR:  relation "sales.orders" does not exist
DROP TABLE on a missing table
ERROR:  table "orders" does not exist
A column qualified with a table that is not in FROM
ERROR:  missing FROM-clause entry for table "orders"
Using the table name after giving it an alias
ERROR:  invalid reference to FROM-clause entry for table "orders"
HINT:  Perhaps you meant to reference the table alias "o".
How psql adds the position (reconstructed from the error position the server reports)
LINE 1: SELECT * FROM customers;
                      ^
SQLSTATE
42P01
Condition name
undefined_table
Product
PostgreSQL
Versions
PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last reviewed
24 October 2026

What it means

SQLSTATE 42P01 means PostgreSQL could not find a table, view, sequence or other relation with that name in the schemas it searched, or a query refers to a table that is not in its FROM clause.

"Relation" is PostgreSQL's word for anything table-like: tables, views, materialized views, sequences, indexes and foreign tables. An unqualified name such as customers is looked up in each schema of the search_path in turn (by default your own schema, then public); a qualified name such as sales.orders only in that schema. If no match is found, the statement fails with 42P01 (undefined_table). Lookup is also exact about case: unquoted names are folded to lower case, so Customers and customers are the same, but a table created as "Customers" (with quotes, as many GUI tools and ORMs do) can only be reached as "Customers". The message shows the name after folding, which is the clue.

Two related messages share this SQLSTATE. "missing FROM-clause entry for table" means a column is qualified with a table name that the query does not list in FROM. "invalid reference to FROM-clause entry" means the table is in FROM under an alias, so only the alias may be used. Finally, every database is separate: a table in another database on the same server is never found, whatever the search_path; check which database you are connected to (a missing database itself is 3D000).

Common causes

  • The table was created with a quoted mixed-case name ("Customers") and is queried unquoted, which looks for customers.
  • The table is in a schema that is not on the search_path (for example sales.orders queried as orders).
  • The schema is on the search_path but the role has no USAGE privilege on it, so PostgreSQL silently skips it.
  • The application is connected to a different database, or a different server or environment, from the one that has the table.
  • The table does not exist yet: a migration has not run, ran in another schema, or a script uses the table before the CREATE TABLE.
  • A typo or a singular/plural mix-up (customer and customers).
  • A column is qualified with a table name that is not in FROM, or with the table name instead of its alias.
  • A temporary table created in another session or connection: temporary tables are visible only in the session that created them, which matters with connection pools.

Diagnose it

Read-only These queries only read the system catalogs (pg_catalog, information_schema) and statistics views such as pg_stat_activity and pg_locks. They do not change data, roles or settings, so they are safe to run first.

Find the table in any schema, ignoring case

SELECT n.nspname AS schema_name, c.relname AS table_name, c.relkind
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE lower(c.relname) = lower('customers')
ORDER BY 1, 2;
What to look for

Each relation with that name in the current database, with its exact case and schema (relkind r = table, v = view, m = materialized view, S = sequence, p = partitioned table). A row with table_name Customers means it needs quotes; a schema other than public means it needs qualifying or a search_path change. No rows means it is not in this database. pg_class lists every relation; information_schema.tables shows only those you have privileges on.

Which database, user and schemas am I using?

SELECT current_database(), current_user, current_schemas(true) AS searched_schemas;

SHOW search_path;
What to look for

The database you are connected to and the schemas PostgreSQL searches for unqualified names, in order. current_schemas shows the effective list: pg_catalog is always searched, and a search_path entry that is not an existing schema, or is a schema you lack USAGE permission on, is silently ignored. If the table's schema is not in the list, qualify the name, change search_path, or check the schema privileges (a qualified name in a schema you cannot use gives 42501).

Test a name without raising an error

SELECT to_regclass('customers')   AS unquoted,
       to_regclass('"Customers"') AS quoted,
       to_regclass('sales.customers') AS qualified;
What to look for

to_regclass returns the relation if the name resolves, or NULL instead of an error, so you can see which spelling works. The ::regclass cast raises 42P01 instead.

How to fix it

Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.

Use the exact name, or rename the table to lower case

Changes schema

Quote a mixed-case name exactly as it was created. Better, rename it once to lower case, so it works quoted or not; update code that already quotes the old name.

SELECT * FROM "Customers";

-- or rename it once
ALTER TABLE "Customers" RENAME TO customers;

Qualify the name with its schema

Read-only

Writing schema.table always finds the table, whatever the search_path. This is the safest choice in scripts, functions and views.

SELECT * FROM sales.orders;

Add the schema to the search_path

Changes configuration

For interactive work, set it for the session. To make it the default for an application, set it on the role or database; it applies to new sessions.

Caution: Putting more schemas on the search_path changes which object an unqualified name finds when two schemas contain the same name. Qualify names in security-sensitive code (such as SECURITY DEFINER functions) instead of relying on search_path.

SET search_path TO sales, public;

-- default for new sessions of one role
ALTER ROLE app_user SET search_path TO sales, public;

Connect to the right database and run pending migrations

Client or network side

Check the database name, host and port in the connection string against the diagnostic output. If the table is genuinely missing, run the migration that creates it, in order, and make scripts create tables before they use them.

Use the alias, and list every table in FROM

Read-only

For the FROM-clause forms: once a table has an alias, qualify its columns with the alias; and add any table you qualify a column with to FROM or a JOIN.

SELECT o.id
FROM sales.orders AS o
WHERE o.id = 1;

Examples

A table created with a quoted mixed-case name

SELECT * FROM "Customers"; works. The message shows customers in lower case because the unquoted name was folded before the lookup.

CREATE TABLE "Customers" (id int, name text);

SELECT * FROM Customers;  -- folded to customers
Result
ERROR:  relation "customers" does not exist

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

A table in a schema that is not on the search_path

CREATE SCHEMA sales;
CREATE TABLE sales.orders (id int, customer_id int);

SELECT * FROM orders;
Result
ERROR:  relation "orders" does not exist

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

Using the table name instead of its alias

CREATE TABLE orders (id int, customer_id int);

SELECT orders.id
FROM orders AS o;
Result
ERROR:  invalid reference to FROM-clause entry for table "orders"
HINT:  Perhaps you meant to reference the table alias "o".

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

The schema is on the search_path, but the role has no USAGE on it

Because app_reader lacks USAGE on sales, that schema is silently left out of the search (current_schemas(true) shows only pg_catalog and public), so the unqualified name is not found. Written as sales.orders, the same query fails with 42501 permission denied for schema sales instead, which points at the real problem. Fix it with GRANT USAGE ON SCHEMA sales TO app_reader; plus SELECT on the table.

CREATE SCHEMA sales;
CREATE TABLE sales.orders (id int);
CREATE ROLE app_reader;

SET ROLE app_reader;
SET search_path TO sales, public;
SELECT * FROM orders;
Result
ERROR:  relation "orders" does not exist

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

How to prevent it

Name tables in lower case with underscores and never quote them, so case can never cause this error; configure ORMs and GUI tools not to quote mixed-case names. Qualify names with the schema in migrations, views and functions, and keep search_path settings explicit per role rather than relying on defaults.

Run migrations as part of deployment and check at start-up that the application is connected to the expected database (for example by logging current_database()). In tests, create every table a script uses before the script runs.

Common questions

Why does PostgreSQL say relation does not exist when the table is there?

Usually because of case or schema. A table created as "Customers" must be queried with quotes, and a table outside your search_path must be qualified (sales.customers). Also check that you are connected to the database that contains it. The first diagnostic query finds the table in any schema regardless of case.

What is a relation in PostgreSQL?

Any table-like object: a table, view, materialized view, sequence, index, foreign table or partitioned table. They share one namespace per schema, so the error says relation rather than table.

How do I drop a table only if it exists?

Use DROP TABLE IF EXISTS name; it issues a notice instead of the error when the table is missing. CREATE TABLE IF NOT EXISTS works the same way for creating.

Can I query a table in another PostgreSQL database?

Not directly: each connection sees one database. Connect to that database, or use an extension such as postgres_fdw to access it through a foreign table.

Learn more on CodeWithSQL

Official documentation

Last technically reviewed 24 October 2026; examples and diagnostics run on PostgreSQL 18.6 (PGlite 0.5.11). SQL-level examples were run in PGlite (PostgreSQL compiled to WebAssembly); examples that need a running server are shown as documented in the PostgreSQL documentation.

Still stuck on a PostgreSQL error?

Browse the full PostgreSQL error library, or get hands-on help from an experienced SQL professional.