PostgreSQL error: relation does not exist
ERROR: relation "users" does not exist
LINE 1: SELECT * FROM users;
^
PostgreSQL found no table, view or sequence with that name among the schemas it is allowed to look in. In practice this error rarely means the table is missing — it usually means you and PostgreSQL are looking in different places.
What this error means
"Relation" is PostgreSQL's umbrella term for tables, views, sequences, indexes and friends. Name resolution depends on two pieces of context people forget they have: the current database (relations live per-database; there is no cross-database access) and the search_path, the ordered list of schemas an unqualified name is resolved against.
Plus one syntax rule that generates endless confusion: unquoted identifiers fold to lowercase. A table created as "Users" (quoted, by an ORM or a GUI) can only ever be reached as "Users" — the unquoted name users is a different identifier.
Common causes
- The table is in a schema not on your
search_path(e.g. it'sapp.usersand your path is"$user", public). - Mixed-case identifier created with quotes; unquoted queries fold to lowercase and miss it.
- Wrong database — connected to the default
postgresdatabase instead of the application one; everything "disappears". - Migrations not applied in this environment, or applied to a different schema than the app queries.
- A temporary table from another session or an earlier connection (temp tables are per-session).
How to diagnose it
SELECT current_database();
SHOW search_path;
-- La relation esiste da qualche parte, sotto qualunque case?
SELECT n.nspname AS schema, c.relname, c.relkind
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname ILIKE '%users%';
That last query settles it in one shot: it tells you whether the relation exists at all, in which schema, and with what exact casing.
How to fix it
- Wrong schema → qualify the name (
app.users) or set the path persistently where it belongs:
(Session-levelALTER ROLE app_user SET search_path = app, public; -- oppure: ALTER DATABASE appdb SET search_path = app, public;SET search_pathworks too, but with connection poolers prefer role/database-level settings.) - Mixed case → quote it exactly (
SELECT * FROM "Users") — and adopt the convention that saves everyone: lowercase, unquoted identifiers only. - Wrong database → fix the connection string;
psqlwithout a database name connects you to one named after your OS user, orpostgres— rarely what the app uses. - Missing migrations → run them; the diagnostic query above confirms whether the schema really is empty.
