ERROR: relation "customers" does not exist when you can see a table called
Customers means the table was created with a quoted, mixed-case name and your
query does not quote it. PostgreSQL folds unquoted names to lower case, so write
"Customers" with the quotes, exactly as it was created.
Look closely at the message: you typed Customers, the error says
customers. That lower-case name in the error is the whole clue. Quote the
identifier in every query, or rename the table to lower case once and stop quoting.
The error
docker exec fix4-pg18 psql -U postgres -d appdb -v VERBOSITY=verbose -f /tmp/e4-select.sql
psql:/tmp/e4-select.sql:1: ERROR: 42P01: relation "customers" does not exist
LINE 1: SELECT * FROM Customers;
^
LOCATION: parserOpenTable, parse_relation.c:1466
The file contains one line, SELECT * FROM Customers;. PostgreSQL 18.4 printed
no hint with it, so the case change in the relation name is the only thing pointing at the
cause.
Why it happens
The SQL standard says unquoted identifiers are case-insensitive, and PostgreSQL implements
that by folding them to lower case before it looks them up. A quoted identifier is taken
literally. The table here was created with
CREATE TABLE "Customers" (id int, name text);, so its stored name is
Customers with a capital C, and \dt lists it that way. The query
SELECT * FROM Customers asks for customers, which is a different
name, and code 42P01 ("undefined table") follows. SQL Server developers meet this most,
because a default SQL Server collation would treat both spellings as the same table.
The usual source of quoted mixed-case names is an ORM. EF Core with the Npgsql provider
keeps the C# names by default and quotes them in the SQL it generates, so a
DbSet<Customer> Customers becomes a table called
"Customers". EF Core's own queries always quote it and work; the error appears
when you open psql, a reporting tool or a raw SQL string and type the name without quotes.
The fix
docker exec fix4-pg18 psql -U postgres -d appdb -v VERBOSITY=verbose -f /tmp/e4-fix.sql
id | name
----+--------------
1 | Maria Garcia
2 | David Chen
(2 rows)
The file now holds SELECT * FROM "Customers";, the identifier quoted exactly
as it was created. fix4-pg18 and appdb are the test's own names.
Quoting has to be exact: "customers" or "CUSTOMERS" would fail the
same way. The longer-term fix is to stop creating mixed-case names: unquoted lower-case or
snake_case tables (customers, order_lines) work with or without
quotes in every tool. For EF Core that means configuring the table and column names, for
example with a naming-convention package, before the first migration rather than after.
How it was reproduced
A fresh postgres:18 container (PostgreSQL 18.4, Debian build) on Docker
Desktop 4.91.0 under Windows 11, database appdb. The table was created and
filled with two fictional rows from a SQL file run with psql -f; the query
files were copied into the container with docker cp, because Windows
PowerShell 5.1 strips double quotes from arguments passed to native programs, which would
have removed the very quotes this post is about.
Frequently asked
- Why does PostgreSQL say relation does not exist when the table exists?
- Most often the table was created with a quoted mixed-case name such as "Customers" and the query uses it unquoted. PostgreSQL folds unquoted names to lower case, so it looks for customers and does not find it.
- Are PostgreSQL table names case sensitive?
- Quoted names are case sensitive and stored exactly as written. Unquoted names are folded to lower case, which makes them behave as case insensitive as long as you never quote them.
- How do I query an EF Core table with a capital letter in psql?
- Put the name in double quotes exactly as EF Core created it, for example SELECT * FROM "Customers". Column names created by EF Core need the same quoting.
More decoded errors in the Fixes category. For a naming rule that avoids this on both PostgreSQL and SQL Server, see naming conventions: snake_case, plurals and case sensitivity.