PostgreSQL

Postgres Column "Does Not Exist" Due to Case Sensitivity Quotes

A column that clearly exists but produces a "does not exist" error almost always comes down to PostgreSQL's specific case-folding rules for unquoted identifiers, colliding with a column that was originally created using quoted, mixed-case naming — the column genuinely exists, just not under the name PostgreSQL thinks you're asking for.

The Problem

A query referencing a column that's clearly visible in the table structure fails:

ERROR:  column "email" does not exist
LINE 1: SELECT Email FROM users;
                ^
HINT:  Perhaps you meant to reference the column "users.Email".

The hint itself is often the giveaway — PostgreSQL is telling you a similarly-named but differently-cased column exists, which is the direct cause of the mismatch.

Why It Happens

PostgreSQL automatically folds unquoted identifiers to lowercase. This means SELECT Email FROM users and SELECT email FROM users and SELECT EMAIL FROM users are all treated identically by PostgreSQL — every one of them is folded to lowercase email before being resolved. This becomes a problem specifically when a column was originally created using double-quoted syntax with mixed case, since quoting preserves the exact case as written, creating a column that genuinely only matches that exact casing:

-- This creates a column with a literal, case-sensitive name "Email"
CREATE TABLE users ("Email" TEXT);

-- This does NOT match the column above - it looks for lowercase "email"
SELECT Email FROM users;
-- ERROR: column "email" does not exist

Common causes of ending up with quoted, mixed-case column names in the first place:

  • A table was created through a tool, ORM, or migration framework that automatically quotes column names using the exact case specified in application code (many ORMs default to preserving casing, like camelCase field names, by quoting them)
  • A table was migrated from a database system with different case-sensitivity rules (like SQL Server, which is case-insensitive by default), and the migration process preserved the mixed-case names literally, with quoting, rather than converting them to PostgreSQL's more common lowercase convention
  • Someone manually created a table using quoted mixed-case names, intentionally or by habit from a different database background, without realizing the ongoing implications for every future query needing to also use quotes with matching case

The Fix

First, confirm the actual, exact stored name of the column:

\d users

Or query the system catalog directly for a scriptable check:

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

If the column shows with quotes or mixed case in the \d output, that confirms it requires exact case-matched, double-quoted references in every query going forward:

SELECT "Email" FROM users;

Note this uses double quotes (identifier quoting) specifically, not single quotes (which are for string literals) — using the wrong quote type here produces a different, unrelated error rather than correctly referencing the column.

For a more permanent, less error-prone fix, rename the column to a standard lowercase, unquoted-compatible name, avoiding the need for quoting in every future query referencing it:

ALTER TABLE users RENAME COLUMN "Email" TO email;

After this rename, the column can be referenced normally without quotes or exact case-matching concerns, which is generally the recommended long-term approach for reducing this entire category of friction across your codebase.

If you're designing new tables and want to avoid this issue arising in the first place, adopt a consistent convention (lowercase with underscores, the standard PostgreSQL convention) and avoid quoted mixed-case identifiers entirely unless you have a specific, deliberate reason requiring them:

-- Recommended: no quotes needed, ever, for any future reference
CREATE TABLE users (email TEXT, created_at TIMESTAMP);

If your application uses an ORM that defaults to quoting camelCase field names automatically, check its configuration for an option to use snake_case column naming instead, which avoids this friction at the framework level rather than needing to manage it manually in every raw SQL query you write outside the ORM's own abstraction.

Still Not Working?

If you're unsure whether a specific column requires quoting without wanting to check the schema manually every time, write a small utility query that lists every column across your schema requiring quoted, case-sensitive access, which is useful both for immediate debugging and for prioritizing which legacy tables might benefit most from a cleanup migration to standard lowercase naming:

SELECT table_name, column_name
FROM information_schema.columns
WHERE column_name != lower(column_name);

Any result here represents a column with mixed-case letters in its actual stored name, confirming it requires exact-case, double-quoted references anywhere it's used in a query.