エラーの内容
ERROR: relation "table_name" does not exist
LINE 1: SELECT * FROM table_name;
^
確かに動くはずのクエリを実行しているのに、PostgreSQL がテーブルは存在しないと言う。pgAdmin ではちゃんと見えているのに。このエラーはほぼ毎回4つの原因のどれかで、データが消えたわけではありません。
なぜこのエラーが起きるか
PostgreSQL がクエリを受け取ったが、テーブルの参照を解決できない状態です。テーブル自体は問題ない場合がほとんどです。原因は大抵、以下のいずれかです:
- テーブルが別のスキーマにあり、
search_pathに含まれていない - 大文字小文字の区別 —
"Users"のようにクォートを使った混合ケースで作成されている - 誤ったデータベースに接続している(複数環境では頻繁に起きる)
- テーブルが本当に存在しない — マイグレーションが実行されていないか、誤った環境で実行された
- 別セッションの一時テーブルである — 一時テーブルはセッションスコープで、他からは見えない
手順別の修正方法
手順1:テーブルが実際に存在するか確認する
まず最初に、テーブルが実際に存在するか確認します:
-- 全スキーマのテーブルを一覧表示
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_name = 'your_table_name';
または psql で:
\dt *.*
結果が出ない場合、テーブルは存在しません。マイグレーションファイルを確認するか、CREATE TABLE 文を手動で実行してください。
手順2:スキーマと search_path を確認する
PostgreSQL はデフォルトで public スキーマを参照します。多くの本番環境では app、api、reporting などのカスタムスキーマを使用しており、search_path にそれらが含まれていない場合、これらのテーブルへのクエリはすべてこのエラーで失敗します。
-- 現在の search_path を確認
SHOW search_path;
-- 現在のスキーマを確認
SELECT current_schema();
テーブルが app というスキーマにある場合、3つの方法があります:
-- オプション A: テーブル名にスキーマを明示する
SELECT * FROM app.users;
-- オプション B: このセッションの search_path にスキーマを追加する
SET search_path TO app, public;
SELECT * FROM users;
-- オプション C: 現在のユーザーに対して永続的に設定する
ALTER ROLE your_user SET search_path TO app, public;
手順3:大文字小文字の区別を確認する
これは誰もが一度は引っかかる落とし穴です。PostgreSQL はクォートなしの識別子をすべて小文字に変換します。そのため Users、users、USERS はすべて同じものとして扱われます。例外はダブルクォートで作成されたテーブルです。
-- このように作成された場合:
CREATE TABLE "Users" (id serial, name text);
-- これは失敗する(PostgreSQL は小文字の "users" を探す):
SELECT * FROM users;
-- これは成功する:
SELECT * FROM "Users";
実際に保存されている名前を確認するには:
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public';
結果に混合ケースの名前が含まれている場合、すべてのクエリでクォートが必要になります。長期的に楽な対処法は、テーブル作成時にアンダースコア区切りの小文字に統一すること — UserAccounts ではなく user_accounts を使いましょう。
手順4:正しいデータベースに接続しているか確認する
PostgreSQL の各データベースは完全に独立しています。db_production 内のテーブルは、同じサーバーでも db_staging への接続からは見えません。これは Docker や CI パイプラインで DATABASE_URL が意図せず別の場所を指している際によく起きる落とし穴です。
-- 現在のデータベースを確認
SELECT current_database();
-- psql でデータベースを切り替える(再接続が必要)
\c correct_database_name
アプリで接続文字列を使用している場合は、.env または設定ファイルのデータベース名を再確認してください。
手順5:ビューまたはシーケンスかどうか確認する
存在しないビューやシーケンスを参照した場合も同じエラーが発生します。すべてのオブジェクトタイプを一度に検索できます:
SELECT schemaname, tablename, 'テーブル' AS type FROM pg_tables WHERE tablename = 'target_name'
UNION ALL
SELECT schemaname, viewname, 'ビュー' FROM pg_views WHERE viewname = 'target_name'
UNION ALL
SELECT schemaname, sequencename, 'シーケンス' FROM pg_sequences WHERE sequencename = 'target_name';
修正の確認
変更後の簡単な確認:
-- 行または空のセットが返るはず — エラーではない
SELECT * FROM your_schema.your_table LIMIT 5;
-- 正しいスキーマとデータベースにいることを確認
SELECT current_schema(), current_database();
ロールの search_path を変更した場合は、一度切断して再接続し、設定が永続化されているか確認してください。
クイックヒント
- テーブル名はアンダースコア区切りの小文字で統一 —
"UserOrders"ではなくuser_orders。大文字小文字の問題を根本的に解消できます。 - スキーマプレフィックスを明示する(
schema.table)方がsearch_pathより信頼性が高い。複数環境で動くアプリコードでは特に有効です。 - マイグレーション実行前に
SELECT current_database(), current_schema();を実行して、正しい場所を対象にしているか確認しましょう。 - Docker や CI 環境では、誤った設定の
DATABASE_URLが間違ったデータベースを指していることが、ローカル開発以外でのこのエラーの最大の原因です。 - Django と SQLAlchemy はどちらも明示的なスキーマ設定をサポートしています。psql では動くのにアプリで失敗する場合は
Meta.db_tableや__table_args__を確認してください。

