できない.dev

PostgreSQL「column does not exist」が列はあるのに出る原因と直し方

column does not exist は、大文字を含む列名を引用符なしで参照すると出る。
列名をダブルクオートで囲んで参照すれば直る。
引用符なしの識別子は小文字へ畳まれるためで、文字列リテラルはシングルクオートで書く。

公開: 更新:

症状できない
psql -U postgres -a -f bad_column.sql
SELECT createdAt FROM users;
psql:bad_column.sql:1: ERROR:  column "createdat" does not exist
LINE 1: SELECT createdAt FROM users;
               ^
HINT:  Perhaps you meant to reference the column "users.createdAt".

2026-10-05 に postgres:17(PostgreSQL 17.11) で実際に打って取った出力。検証環境の詳細

要約

テーブルに列が存在するのに ERROR: column "createdat" does not exist になる場合、ほとんどは大文字小文字と引用符の問題である。

PostgreSQL では引用符なしの識別子は自動的に小文字へ畳まれる。
一方、ダブルクオート付きで作られた識別子は大文字小文字がそのまま保存される。createdAt という列を引用符なしで参照すると createdat を探しに行くため、一致せずにこのエラーになる。

実行例

"createdAt" と引用符付きで作った列を SELECT createdAt と引用符なしで参照すると、エラーの列名は小文字の createdat になり、HINT に users.createdAt を参照するつもりではないかという候補が添えられた。\d users の定義にも createdAt と大文字混じりで表示され、ダブルクオートで書き直すと値が返り、WHERE name = "alice" は column "alice" does not exist になるがシングルクオートにすると 1 行が取れている。

$ psql -U postgres -a -f bad_column.sql
SELECT createdAt FROM users;
psql:bad_column.sql:1: ERROR:  column "createdat" does not exist
LINE 1: SELECT createdAt FROM users;
               ^
HINT:  Perhaps you meant to reference the column "users.createdAt".
$ psql -U postgres -c '\d users'
                           Table "public.users"
  Column   |            Type             | Collation | Nullable | Default 
-----------+-----------------------------+-----------+----------+---------
 id        | integer                     |           |          | 
 name      | text                        |           |          | 
 createdAt | timestamp without time zone |           |          |
$ psql -U postgres -a -f solve_quote.sql
SELECT "createdAt" FROM users;
      createdAt      
---------------------
 2026-01-01 09:00:00
(1 row)
$ psql -U postgres -a -f bad_literal.sql
SELECT * FROM users WHERE name = "alice";
psql:bad_literal.sql:1: ERROR:  column "alice" does not exist
LINE 1: SELECT * FROM users WHERE name = "alice";
                                         ^
$ psql -U postgres -a -f solve_literal.sql
SELECT * FROM users WHERE name = 'alice';
 id | name  |      createdAt      
----+-------+---------------------
  1 | alice | 2026-01-01 09:00:00
(1 row)

— 2026-10-05 時点の出力

検証環境PostgreSQL 17.11 / 2026-10-05 検証
検証日
実行環境
postgres:17 Debian GNU/Linux 13 (trixie)
バージョン
  • PostgreSQL 17.11

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

解決策

1. エラーメッセージの列名を手がかりにする

SELECT createdAt FROM users;
-- ERROR:  column "createdat" does not exist

エラー中の列名が全部小文字になっていたら、畳み込みが起きたサインである。

2. 大文字を含む列はダブルクオートで参照する

SELECT "createdAt" FROM users;

引用符付きで作られた識別子は、参照時も毎回同じ引用符付きで書く必要がある。

3. 実際の列名を確認する

\d users

psql の \d はテーブル定義を実際の大文字小文字のまま表示するので、どちらの形で作られたかを確定できる。

4. 文字列はシングルクオートで書く

SELECT * FROM users WHERE name = 'alice';

なお SQL 標準では引用符なしの識別子は大文字へ畳まれる規定で、PostgreSQL の小文字畳み込みは標準と逆である。
移植性を考えるなら「常に引用符を付ける」か「常に付けない(小文字スネークケース)」のどちらかに統一するのが安全で、新規設計なら後者が扱いやすい。

よくある原因

  1. 小文字への畳み込み: SELECT createdAt FROM users と書くと、PostgreSQL は createdat という列を探す(字句構造の公式ドキュメント(新しいタブで開く))。
  2. ORM が引用符付きで作成: Prisma や TypeORM などは camelCase の列を "createdAt" のように引用符付きで作るため、手書き SQL 側で引用符が必要になる。
  3. リテラルの引用符違い: PostgreSQL でダブルクオートは識別子用で、WHERE name = "alice" は文字列比較ではなく alice という列の参照になる。
  4. search_path 違い: 同名テーブルが複数スキーマにあり、列の無い方を見ている。

この手順で直った?