PostgreSQL

How to Fix "PostgreSQL Column Does Not Exist" Casing/Quote Issue

4 min read by DebuggedIt

Quick answer

A query references a column you can clearly see in your table definition, but PostgreSQL insists it doesn't exist. This is almost always a case-sensitivity...

A query references a column you can clearly see in your table definition, but PostgreSQL insists it doesn't exist. This is almost always a case-sensitivity trap β€” PostgreSQL silently lowercases unquoted identifiers, so a column created with mixed case using quotes behaves very differently from how it looks in your schema file or an ORM model.

The Problem

A query fails even though the column is visible in \d output or a migration file:

mydb=# SELECT "userId" FROM users;
ERROR:  column "userid" does not exist
LINE 1: SELECT "userId" FROM users;
               ^
HINT:  Perhaps you meant to reference the column "users.userId".

Or the reverse β€” an unquoted query fails against a column that was created with quotes and mixed case:

mydb=# SELECT userId FROM users;
ERROR:  column "userid" does not exist
LINE 1: SELECT userId FROM users;
               ^
Unquoted identifiers get folded to lowercase CREATE TABLE users ( userId INT) -> stored as: userid CREATE TABLE users ( "userId" INT) -> stored as: userId Match the quoting style consistently everywhere the column is referenced or avoid mixed case entirely and use snake_case

Why It Happens

PostgreSQL's identifier folding rule is simple but easy to forget: any unquoted identifier is automatically lowercased when the statement is parsed, while a quoted identifier ("userId") preserves its exact case as written. This means:

  • CREATE TABLE users (userId INT); actually creates a column named userid, all lowercase, regardless of how you typed it β€” because the identifier was unquoted.
  • CREATE TABLE users ("userId" INT); creates a column genuinely named userId with mixed case preserved β€” because the identifier was quoted.
  • Once a column exists as userId (quoted, mixed case), every future reference to it must also be quoted and exactly matching case, or PostgreSQL will look for userid instead and fail to find it.
  • This is a common trap when an ORM (particularly ones with a Java or JavaScript background, like Hibernate or some TypeORM configurations) generates quoted, camelCase column names by default, while raw SQL written elsewhere in the same project uses unquoted, expecting-lowercase conventions.

The Fix

First, check exactly how the column is actually stored, case and all:

\d users
              Table "public.users"
 Column  |  Type   | Collation | Nullable | Default
---------+---------+-----------+----------+---------
 userId  | integer |           | not null |

If it shows as userId with mixed case, you must quote it exactly that way in every query:

SELECT "userId" FROM users;

If you'd rather avoid this entirely going forward, the cleanest long-term fix is renaming to lowercase (or explicit snake_case) so you never have to think about quoting again:

ALTER TABLE users RENAME COLUMN "userId" TO user_id;

After renaming, plain unquoted references work naturally, since PostgreSQL's default lowercasing now matches the actual stored name:

SELECT user_id FROM users;

If you're using an ORM, configure it consistently rather than mixing conventions across your codebase β€” most modern ORMs default to snake_case column naming specifically to sidestep this PostgreSQL quirk entirely:

# Example: SQLAlchemy explicit naming convention
class User(Base):
    __tablename__ = "users"
    user_id = Column(Integer, primary_key=True)  # maps to user_id, no quoting needed

Still Not Working?

If you're still unsure exactly how a column is stored, query PostgreSQL's system catalog directly rather than relying on visual inspection of a migration file, since the actual stored name is the only thing that matters at query time regardless of what the original CREATE TABLE statement looked like when written:

SELECT column_name FROM information_schema.columns WHERE table_name = 'users';

The column_name values returned here are exactly what you need to reference in queries β€” if a name comes back with mixed case, quote it exactly as shown; if it comes back all lowercase, an unquoted reference will work fine.

It's also worth knowing that this same folding behavior applies to table names, schema names, and even database names, not just columns β€” the exact same trap can happen with SELECT * FROM "MyTable" failing against a table actually named mytable, for entirely analogous reasons. If you're migrating from a database system with different case-sensitivity rules (SQL Server and MySQL on case-insensitive filesystems both behave quite differently here), it's worth deciding on a single naming convention early and enforcing it consistently across every migration and every piece of application code, rather than discovering these mismatches one error at a time as your schema grows:

-- A reasonable house rule: never quote identifiers, always use snake_case
CREATE TABLE order_items (
    order_item_id SERIAL PRIMARY KEY,
    order_id INTEGER NOT NULL,
    unit_price NUMERIC(10,2)
);

Following this convention consistently means you never need to think about quoting at all β€” every identifier folds to lowercase the same way, every time, and unquoted references in queries always just work without any special-casing needed anywhere in your codebase or ORM configuration.