PostgreSQL 15 min read

PostgreSQLの「relation does not exist」 ― テーブルはあるのにエラーになる7つの原因

PostgreSQL で、作ったはずの表を SELECT すると次のエラーになることがあります。

ERROR:  relation "orders" does not exist
LINE 1: SELECT * FROM orders;
                      ^

SQLSTATE は 42P01 です。表が本当に無い場合のほか、表はあっても今の接続から見えない場合にも同じエラーが出ます。この記事のログは PostgreSQL 17.6 で採りました(\set VERBOSITY verbose で SQLSTATE を出しています。LOCATION: 行は省きました)。

まず見る(結論)

エラーになった接続のまま、次の3つを実行します。

SELECT current_database(), current_user;
SHOW search_path;
SELECT to_regclass('app.orders');
  • データベースが想定と違う → 接続先の間違い(原因3)
  • スキーマ名を付けた to_regclass が NULL でない → 表はある。search_path か権限の問題(原因1・4)
  • スキーマ名を付けても NULL → 表名の大文字小文字か、表が本当に無い(原因2)

表がどのスキーマにあるか分からなければ、psql で \dt *.orders を打つと全スキーマから探せます。

原因と対処の一覧

原因 見分け方 対処
1. 表が search_path に無いスキーマにある SHOW search_path にそのスキーマが無い スキーマ名を付けて書く。または search_path に足す
2. 大文字を含む名前を引用符付きで作った \dt の Name に大文字が出る "Users" と二重引用符で書く
3. 別のデータベースに接続している current_database() が違う 正しいデータベースに接続し直す
4. スキーマの USAGE 権限が無い スキーマ名付きなら permission denied for schema GRANT USAGE ON SCHEMA
5. 一時表を別のセッションから引いた 作ったセッションでは引ける 同じセッションで使う。または通常の表にする
6. シーケンス名を推測で書いた nextval('...') の行でエラー pg_get_serial_sequence() で実名を引く
7. ロールかデータベースに search_path が設定されている 接続ユーザーによって結果が変わる \drds で設定を確認する

1. 表が search_path に無いスキーマにある

スキーマ名を省いた表は、search_path に並んだスキーマから順に探されます。既定値は次のとおりです。

appdb=# SHOW search_path;
   search_path
-----------------
 "$user", public
(1 row)

appdb=# SELECT current_schemas(true);
   current_schemas
---------------------
 {pg_catalog,public}
(1 row)

"$user" は接続ユーザーと同じ名前のスキーマです。ここでは postgres スキーマが無いので、current_schemas の結果から外れています。

表 orders をスキーマ app に作ると、スキーマ名を省いた SELECT は失敗します。

appdb=# SELECT * FROM orders;
ERROR:  42P01: relation "orders" does not exist
LINE 1: SELECT * FROM orders;
                      ^

app.orders と書くか、search_path に app を足すと引けます。

appdb=# SET search_path = app, public;
SET
appdb=# SELECT * FROM orders;
 id | item
----+-------
  1 | item1
  2 | item2
(2 rows)

SET はそのセッションの中だけで効きます。毎回の接続で効かせる方法は原因7に書きました。

psql の \dt も search_path の中しか表示しません。\dt には public."Users" だけが出て、app.orders は \dt app.* で初めて出ました。\dt に出なくても、表が無いとは限りません。

2. 大文字を含む名前を引用符付きで作った

PostgreSQL は引用符の無い名前を小文字にしてから探します。CREATE TABLE "Users" のように二重引用符で作った表は、名前が Users のまま登録されます。

appdb=# SELECT * FROM Users;
ERROR:  42P01: relation "users" does not exist
LINE 1: SELECT * FROM Users;
                      ^
appdb=# SELECT * FROM "Users";
 id | name
----+-------
  1 | Alice
(1 row)

users と書いても同じエラーです。Users と書いてもメッセージには小文字の "users" が出ます。メッセージの名前と自分が書いた名前の大文字小文字が違っていたら、この原因です。ORM や GUI ツールが作った表でよく起きます。

3. 別のデータベースに接続している

PostgreSQL の SQL は、接続しているデータベースの中の表しか引けません。appdb に作った表を postgres データベースから引くと、スキーマ名を付けても同じエラーになります。

postgres=# SELECT * FROM app.orders;
ERROR:  42P01: relation "app.orders" does not exist
LINE 1: SELECT * FROM app.orders;
                      ^

psql のプロンプトにはデータベース名が出ますが、アプリの接続文字列やツールの既定値で postgres に繋がっていることがあります。

4. スキーマの USAGE 権限が無い

権限の無いユーザー app_user で app.orders を引いたときの結果は、スキーマ名を書いたかどうかで変わりました。

appdb=> SELECT * FROM app.orders;
ERROR:  42501: permission denied for schema app
LINE 1: SELECT * FROM app.orders;
                      ^
appdb=> SET search_path = app, public;
SET
appdb=> SELECT * FROM orders;
ERROR:  42P01: relation "orders" does not exist
LINE 1: SELECT * FROM orders;
                      ^

search_path に app を入れても、USAGE 権限の無いスキーマは探す対象から外れます。権限の問題なのに、メッセージは relation does not exist になります。スキーマ名を付けて実行し直すと permission denied が出るので、切り分けに使えます。

USAGE だけ付けると、今度は表の権限で止まります。

appdb=# GRANT USAGE ON SCHEMA app TO app_user;
GRANT
appdb=> SELECT * FROM app.orders;
ERROR:  42501: permission denied for table orders

表にも GRANT SELECT ON app.orders TO app_user; を付けると引けました。

PostgreSQL 15 からは一般ユーザーが public スキーマに表を作れません。app_user で CREATE TABLE public.t2(x int); を実行すると ERROR: 42501: permission denied for schema public になりました。作成に失敗したことに気づかないと、後の SELECT で relation does not exist になります。

5. 一時表を別のセッションから引いた

CREATE TEMP TABLE で作った表は、作ったセッションからしか見えません。セッション A で CREATE TEMP TABLE t1(x int); を実行し、別のセッション B で引くとエラーになります。

appdb=# SELECT * FROM t1;
ERROR:  42P01: relation "t1" does not exist
LINE 1: SELECT * FROM t1;
                      ^

コネクションプールを使うアプリでは、一時表を作った接続と次の SQL を投げる接続が別になることがあります。一時表はセッションが終わると消えます。

6. シーケンス名を推測で書いた

シーケンスも relation の一種なので、名前を間違えると同じエラーです。

appdb=# SELECT nextval('orders_id_seq');
ERROR:  42P01: relation "orders_id_seq" does not exist
LINE 1: SELECT nextval('orders_id_seq');
                       ^

serial や IDENTITY の列に付くシーケンスの名前は、SELECT pg_get_serial_sequence('app.orders', 'id'); で引けます。nextval の引数も search_path で探されるので、スキーマ名を付けて渡します。

7. ロールかデータベースに search_path が設定されている

search_path はロールやデータベースに設定できます。効くのは新しく接続したセッションからで、接続中のセッションの値は変わりませんでした。

ALTER ROLE app_user SET search_path = app, public;
ALTER DATABASE appdb SET search_path = app, public;

両方を設定すると、ロールの設定が優先されました。データベースに app, public、app_user に public を設定した状態で新しく接続した結果です。

-- postgres で接続(ロールの設定なし)
appdb=# SHOW search_path;
 search_path
-------------
 app, public
(1 row)

-- app_user で接続(ロールに public を設定)
appdb=> SHOW search_path;
 search_path
-------------
 public
(1 row)

同じ SQL が、あるユーザーでは通り、別のユーザーでは relation does not exist になる場合は、この設定の違いを疑います。psql の \drds でロールとデータベースごとの設定を一覧できます。

表があるかを SQL で確かめる

to_regclass() は、名前を今の search_path で解決して、見つからなければエラーではなく NULL を返します。スクリプトの中で存在を確かめるのに使えます。search_path が既定のときの結果です。

appdb=# SELECT to_regclass('orders'), to_regclass('app.orders'),
appdb-#        to_regclass('"Users"'), to_regclass('users');
 to_regclass | to_regclass | to_regclass | to_regclass
-------------+-------------+-------------+-------------
             | app.orders  | "Users"     |
(1 row)

引数の書き方は SQL の中で表名を書くときと同じです。大文字を含む名前は、文字列の中でも二重引用符で囲みます。