Postgres "Relation Does Not Exist" Error Fix
The "relation does not exist" error in PostgreSQL doesn't necessarily mean the table was never created — it very often means Postgres is looking in the wrong schema, or the table name's case doesn't match what was actually used when it was created.
The Problem
A query against a table you're confident exists fails with:
ERROR: relation "orders" does not exist
LINE 1: SELECT * FROM orders;
Running \dt in psql might even show the table listed, which makes the error more confusing rather than less — the table clearly exists somewhere, just apparently not where this specific query is looking.
Why It Happens
Unlike some databases, PostgreSQL organizes tables within schemas, and has specific, sometimes surprising rules about case sensitivity. Common causes:
- The table exists in a different schema than the one currently active in your session's
search_path, so an unqualified table name doesn't resolve to it - The table was created with quoted, mixed-case identifiers (like
"Orders"), and PostgreSQL treats quoted identifiers as case-sensitive — queryingorderswithout quotes looks for a completely different, lowercase-only identifier - You're connected to a different database than the one where the table actually lives, especially easy to happen when multiple databases exist on the same PostgreSQL instance with similar names
- The table genuinely was never created because an earlier migration or script silently failed, and its apparent "existence" was actually a misreading of the migration tool's success message
The Fix
First, confirm which database and schema you're actually connected to:
SELECT current_database(), current_schema();
Check the currently active search path, which determines where unqualified table names are resolved from:
SHOW search_path;
If the table lives in a schema not included in this path, either add that schema to the search path for your session, or reference the table with its full schema-qualified name to bypass the search path entirely:
SELECT * FROM my_schema.orders;
If case sensitivity is the issue, check how the table was actually created by listing tables with their exact stored names:
\dt
If the listing shows "Orders" with capitalization, you must use double quotes and match the exact case in every query referencing it going forward:
SELECT * FROM "Orders";
This is a common trap: PostgreSQL folds unquoted identifiers to lowercase automatically, but once an identifier is created with quotes preserving mixed case, every future reference must also be quoted with matching case, or it won't resolve. Because of this, it's generally recommended to avoid quoted, case-sensitive identifiers in new schemas entirely, sticking to lowercase names without quotes to sidestep the issue going forward.
Confirm you're connected to the correct database, not just the correct server:
\l
\c correct_database_name
It's easy to have multiple similarly-named databases (like myapp_dev, myapp_test, myapp) on the same server, and connecting to the wrong one produces exactly this error for tables that genuinely exist, just in a different database.
Still Not Working?
If none of the above resolves it, confirm the table genuinely was created successfully by checking the migration or setup script's actual execution log, rather than trusting a summary message alone. Re-run the relevant CREATE TABLE statement directly and check for any error output — a migration tool that reports overall "success" doesn't always guarantee every individual statement within it actually executed without error, especially if error handling within the tool itself has gaps.
It's also worth checking whether you're connected through a connection pooler like PgBouncer configured in a mode that limits visibility into schema changes made in a different session. In transaction pooling mode specifically, some session-level state (including certain search path configurations) doesn't behave identically to a direct connection, which can produce subtle discovery issues that look like a missing table but are really a pooling-mode quirk.
Finally, double-check for trailing whitespace or invisible characters in the table name if it was copied from another source (a spreadsheet, a chat message, documentation) rather than typed directly — a table name with a trailing space embedded in it when originally created would require that exact same trailing space to be referenced correctly, which is nearly impossible to spot visually but easy to introduce accidentally through copy-paste.