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 の中で表名を書くときと同じです。大文字を含む名前は、文字列の中でも二重引用符で囲みます。