ERROR: relation "users" does not exist は、PostgreSQL がそのテーブル(やビュー・シーケンス)を見つけられなかったときのエラーです。 テーブルが本当に無いとは限らず、名前の大文字小文字・スキーマ・接続先のデータベースの違いで「見えていない」ことがほとんどです。

エラー文の "users" は、PostgreSQL が実際に探した名前です。書いた名前と違っていないかを最初に見ます。

原因 見分け方 直し方
大文字で作ったテーブル("Users") FROM Users と書いたのに、エラー文は "users"(小文字) FROM "Users" と引用符で囲む。または小文字の名前で作り直す
別のスキーマにある(app.items) 下の SQL で探すと、public 以外のスキーマにある app.items と書く、または search_path を設定
別のデータベースにつないでいる current_database() が想定と違う 接続先のデータベース名を直す
作った接続が、まだコミットしていない 別の接続からだけ見えない 作った側でコミット

確認環境:PostgreSQL 16.15、psycopg 3.3.6(確認日 2026年10月11日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PostgreSQL relation does not existの原因と直し方」

まず確認:テーブルは本当にあるか

SELECT current_database(), current_schemas(true);
SELECT table_schema, table_name
FROM information_schema.tables
WHERE lower(table_name) IN ('users', 'items', 'orders')
ORDER BY 1, 2;
reldemo | {pg_catalog,public}          ← 接続先のデータベースと、探す対象のスキーマ

app    | items                          ← items は app スキーマにある
public | Users                          ← Users は大文字の U で作られている
public | orders

lower(table_name) で探すと、大文字小文字に関係なく見つかります。 ここで見つかった「スキーマ」と「正確な名前」で、どの原因かが分かります。psql なら \dt *.* でも一覧を出せます(システムのテーブルも大量に表示されます)。

原因1:大文字で作ったテーブル

CREATE TABLE "Users" (id int, name text);     -- 引用符つきで、大文字の U
SELECT * FROM Users;
ERROR:  relation "users" does not exist
LINE 1: SELECT * FROM Users;

Users と書いたのに、エラー文は "users" になっています。 PostgreSQL は、引用符で囲まない名前を小文字に変えてから探します(PostgreSQL 公式ドキュメント)。"Users" という名前のテーブルは、引用符で囲んだときだけ見つかります。

書き方 探す名前 "Users" で作ったテーブル orders で作ったテーブル
Users・users・USERS users 見つからない —
"Users" Users 見つかる —
orders・ORDERS orders — 見つかる
"Orders" Orders — 見つからない

公式ドキュメントでは、FOO・foo・"foo" は同じ、"Foo"・"FOO" はそれぞれ別の名前とされています。一度大文字を含む名前(引用符つき)で作ると、以降ずっと引用符が必要です。

直し方 書き方
毎回引用符で囲む SELECT * FROM "Users"(Java の文字列なら "SELECT * FROM \"Users\"")
小文字の名前に変える(おすすめ) ALTER TABLE "Users" RENAME TO users;

大文字を含む名前でテーブルを作るツール・ORM・移行ツールを使っていると、この形になります。MySQL や SQL Server から移した DDL で、名前を引用符で囲んで作った場合も同じです。PostgreSQL では、テーブル名・列名は小文字とアンダースコアにそろえるのが無難です。

原因2:別のスキーマにある(search_path)

CREATE SCHEMA app;
CREATE TABLE app.items (id int);
SELECT * FROM items;
ERROR:  relation "items" does not exist
SHOW search_path;
   search_path
-----------------
 "$user", public              ← app スキーマは探す対象に入っていない

スキーマ名を付けずに書いたテーブルは、search_path の順にスキーマを探します(公式ドキュメント)。既定は「ユーザー名と同じスキーマ」と public で、ほかのスキーマのテーブルは、存在していても見つかりません。

直し方 書き方 範囲
スキーマ名を付ける SELECT * FROM app.items; その SQL だけ
その接続で設定 SET search_path TO app, public; その接続の間
ロール(ユーザー)に設定 ALTER ROLE tx SET search_path TO app, public; 次に接続したときから
データベースに設定 ALTER DATABASE reldemo SET search_path TO app, public; 次に接続したときから

確認環境でも、ALTER ROLE で設定したあと、接続し直すと search_path が app, public になり、items が見つかりました。アプリの接続 URL に設定する方法もあります(JDBC なら currentSchema=app)。

原因3:接続先のデータベースが違う

(reldemo で作った orders を、txdemo に接続して検索)
ERROR:  relation "orders" does not exist

PostgreSQL では、データベースが違うと、テーブルは見えません(スキーマ名を付けても見えない)。開発用と本番用、アプリと psql で、接続先のデータベース名が違っていないかを SELECT current_database(); で確かめます。

原因4:作った接続が、まだコミットしていない

(接続 A で CREATE TABLE newtab、まだコミットしていない)
(接続 B から SELECT * FROM newtab)
UndefinedTable: relation "newtab" does not exist
(接続 A でコミット後)
(接続 B から SELECT * FROM newtab)→ OK

PostgreSQL では、CREATE TABLE もトランザクションの中で実行されます。 作った接続でコミットするまで、ほかの接続からは見えませんでした。マイグレーションのツールやテストで、作成とコミットのタイミングがずれていないかを確かめます。

似たエラー:権限が無い

ERROR:  permission denied for table secret

テーブルはあるが、権限が無いときは、does not exist ではなく permission denied になりました。別の問題なので、GRANT で権限を確かめます。

確認の手順(チェックリスト)

順番 確認すること 方法
1 エラー文の名前 "users" のように、小文字になっていないか
2 テーブルの実際の名前とスキーマ information_schema.tables を lower(table_name) で探す
3 大文字の名前 "Users" と引用符で囲む、または小文字に変える
4 スキーマ スキーマ名を付ける、search_path を設定
5 データベース SELECT current_database();
6 コミット 作った接続でコミットしたか

よくある質問

Q. SQLSTATE は何ですか。 A. 42P01(undefined_table)です。psycopg では UndefinedTable の例外になります(確認環境でも同じ)。

Q. シーケンスでも同じエラーになりますか。 A. なります。relation "users_id_seq" does not exist のように、テーブル以外(シーケンス・ビュー・インデックス)でも relation と表示されます。名前とスキーマを同じ手順で確かめます。

まとめ

  • テーブルが見つからないエラー。多くは名前・スキーマ・データベースの違い
  • 引用符なしの名前は小文字に変えて探す。"Users" で作ったら、"Users" と書く(小文字に変えるのがおすすめ)
  • 別のスキーマのテーブルは、search_path に無いと見つからない。スキーマ名を付けるか、設定する
  • データベースが違うと見えない。current_database() で確かめる
  • CREATE TABLE もコミットまでほかの接続から見えない
  • 権限が無いときは permission denied(別のエラー)

参考・出典

確認日はいずれも 2026年10月11日です。

  • PostgreSQL 16 Documentation「Lexical Structure」(Identifiers and Key Words):https://www.postgresql.org/docs/16/sql-syntax-lexical.html
  • PostgreSQL 16 Documentation「Schemas」(The Schema Search Path):https://www.postgresql.org/docs/16/ddl-schemas.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PostgreSQL relation does not existの原因と直し方」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?