目次
先に結論:406だけで原因を決めない
SupabaseのData APIでHTTP 406とPGRST116を受け取ったら、まずレスポンス本文のcode、details、hint、messageを保存し、どのクエリがどの形の結果を要求したかを確認します。PGRST116は、単数形レスポンスを要求したのに返却対象が0件または複数件だったことを示します。したがって、このコードだけから「RLSが原因」「行が存在しない」「重複している」のどれか一つに決めることはできません。
診断の順番は、①要求した結果件数、②フィルターと対象データ、③現在のユーザーから見える行、④操作が返すデータの設定、⑤.single()など返却形式の選択です。期待する件数と実際に見える件数を分けて確かめれば、エラーを隠さず原因を絞れます。
PGRST116が示すこと、示さないこと
Supabase公式のエラーコード表では、PGRST116はsingular responseを要求した際に0件または1件を超える結果が返った場合の406です。たとえば.single()は、結果がちょうど1行であることを要求します。0行でも2行以上でも、その契約を満たさないためエラーになります。
このエラーは返却件数と要求形式の不一致を示します。データベース全体に行が存在するか、現在のセッションから行が見えるか、WHERE条件が正しいか、更新が実行されたかを単独では証明しません。detailsに「0 rows」などの説明があっても、それはそのリクエストの実行主体・条件・返却形で観測された件数です。
エラーを握りつぶす前に、呼び出し元が本当に1件だけを期待しているかを確認します。必須の設定レコードを1つ取得する処理なら、0件は初期化漏れ、誤ったID、権限やRLSの可視性などを調べるべき異常かもしれません。一方、検索結果が0件でも正常な画面なら、単数返却を要求しているコードが仕様と合っていない可能性があります。
まずリクエストと対象を固定する
再現時は、テーブル名、選択列、フィルター、認証状態、実行した操作、レスポンス全体を同じ記録にまとめます。トークン、メールアドレス、個人情報、行の機微な内容はログへ残さず、必要な値だけをマスキングしてください。問題を再現するために、ブラウザのNetworkやサーバーログから秘密情報を含むAuthorizationヘッダーを共有してはいけません。
次に、フィルター値とデータ型を見直します。UUIDの取り違え、前後の空白、大小文字、tenant_idやuser_idの条件漏れ、NULLの比較、日付範囲の境界、JOINによる行の増幅は、想定外の件数を生みます。クライアント画面にIDが表示されていても、それが現在の環境・テーブル・ユーザーに属するかを確認してください。
調査では、アプリケーションが使うのと同じ認証主体・JWT claim・環境で読み取りを再現します。管理者権限やservice roleで同じクエリを実行した結果は、一般ユーザーの可視性を示す証拠にはなりません。強い権限で行が見つかったとしても、そのまま本番クライアントへ強いキーを移したり、RLSを無効化したりしないでください。
0件だったとき:存在・条件・可視性を分ける
0件なら、最初にフィルターなしで広く読むのではなく、安全な範囲で対象キーと条件を確認します。IDを一文字ずつ照合し、対象環境(開発・ステージング・本番)、スキーマ、テーブル、親子関係、tenant条件を見ます。アプリが持つIDと、実際に問い合わせているIDをログ上で照合できるようにします。
そのうえで、同じユーザーセッションでselectし、対象行が返るかを確認します。行が見えないときは、「DBにない」と「このロールから見えない」を区別できているかがポイントです。SupabaseのRow Level Securityはポリシーによってユーザーが参照できる行を絞り込みます。RLSが有効なテーブルでポリシー条件が現在のユーザーやtenantと合わなければ、対象行が存在してもアプリからは0行に見えることがあります。
RLSを調べるときは、テーブルのRLS有効状態、SELECTポリシーのUSING条件、JWT内の必要なclaim、ログインユーザーの識別子、tenantの紐づけを順に確認します。ポリシーを広げて直るか試す、RLSを一時停止する、service roleキーをフロントエンドで使う、といったテストは避けてください。最小権限の開発環境とテストユーザーで再現し、ポリシーの意図どおりに許可・拒否されるかを確認します。
更新や削除のあとに0件のように見えるケースもあります。SupabaseのJavaScriptクライアントでは、update()は既定で変更行をレスポンスに含めません。変更後の行を確認したい場合、フィルターを付けた更新に.select()を連結して返却を要求します。返却データを要求していないのにレスポンスが空だったことを、更新失敗やRLS拒否の証拠と解釈しないでください。逆に.select()を要求した結果が空なら、そのユーザーから返却可能な行があったか、操作・フィルター・ポリシーを確認します。
読み取りと書き込みを混同しないことも重要です。まず、同じ認証状態で対象行を読む操作が何件返すかを確かめます。次に、変更処理がどの条件の行を対象にしたか、.select()で返却を要求したかを分けて確認します。返却される最小限の列だけを選び、意図しない個人情報をクライアントへ返さない設計を維持してください。
複数件だったとき:重複か、期待形式の誤りか
複数件なら、まずフィルターが業務上の一意なレコードを指しているかを確認します。ユーザーごとの設定を1件だけ取得する設計なのに同じuser_idの行が複数あるなら、データ整合性、制約、作成処理の再試行を調べる必要があります。limit(1)を加えるとエラーが消えることはありますが、どのレコードを選ぶべきか決めずに使うと重複や曖昧さを隠します。
一方、一覧や履歴のように複数件が正しい処理なら、単数形レスポンスを要求する方が誤りです。配列を扱う通常のselect結果として受け取り、画面側で0件、1件、複数件の状態を分けます。データベースの一意制約が必要かどうかは、プロダクト上の意味を確かめてから決めます。似た名前や同じuser_idだけを理由に一意制約を追加してはいけません。
.single()、.maybeSingle()、配列を選ぶ
single()は結果が厳密に1件であるときに使います。必須のプロフィール行、指定IDの詳細など、1件が要件であり、0件も複数件も調査すべき異常なら適切です。エラーを捕まえた後は、ユーザーに出す文面と運用ログを分け、内部のSQLや機微情報をそのまま表示しないようにします。
maybeSingle()は0件または1件が正常で、2件以上は不整合として扱いたい場合に選びます。任意設定の取得など、「まだ作成されていない」状態が仕様上許されるケースが候補です。0件をnull相当として処理する場合も、その後の画面や保存処理が安全に動くことを確認します。期待する行が必須なのに安易にmaybeSingle()へ替えると、初期化漏れを見えなくするため避けてください。
配列は件数が0から複数まで変わる検索や一覧に使います。必要なフィルターと順序を明示し、ページングや空状態を仕様に合わせます。複数件を単数に変換するのではなく、利用側が複数の結果を扱えるようにします。
表は横にスクロールできます
| 期待する結果 | 選ぶ返却形式 | 0件の扱い | 複数件の扱い |
|---|---|---|---|
| 必ずちょうど1件 | single() | 異常として原因調査 | 異常として重複・条件確認 |
| 0件か1件 | maybeSingle() | 正常な未設定として扱えるか確認 | 重複として調査 |
| 0件以上の一覧 | 配列 | 空一覧として扱う | 一覧として全件またはページ分を扱う |
選択基準はエラーを消す方法ではなく、Reader Jobやアプリケーション要件です。戻り値の型だけを変えても、フィルターの誤り、RLSの不備、重複データは解決しません。
安全な確認チェックリスト
-
レスポンスを識別する:HTTP status、
code、details、hint、messageを記録し、PGRST116であることを確かめる。別のエラーコードならこの手順だけで判断しない。 -
結果件数の前提を書く:対象クエリは必須1件、任意0〜1件、一覧のどれかを決める。呼び出し箇所の仕様と
.single()等の使い方を照合する。 -
クエリを検証する:ID、テーブル、環境、フィルター、tenant条件、JOIN、NULL、日付境界を確認する。データを広く公開したりフィルターを外したりせず、テスト環境で対象キーに絞って確認する。
-
認証主体と可視性を確かめる:アプリと同じユーザー・roleで再現し、SELECT権限とRLSポリシー、必要なclaimを点検する。強い権限での結果は一般ユーザーの結果と分けて記録する。
-
mutationの返却を分ける:更新・削除を行ったか、返却を
.select()で要求したかを確認する。返却されないことと、変更されていないことを同一視しない。 -
返却形式を要件に合わせる:単数必須なら
single()、任意単数ならmaybeSingle()、複数可能なら配列。形式を変えたら、空・1件・複数件の各ケースをテストする。 -
修正後に回帰確認する:通常ユーザーと権限の異なるテストユーザーで、許可される行と見えてはいけない行を確認する。失敗ログに認証トークン、秘密鍵、個人情報を残さない。
よくある誤診と避け方
**「406だからRLSが原因」**とは限りません。PGRST116の定義は単数レスポンスの件数不一致です。RLSは0件に見える状況を作る可能性があるため、同じユーザーでのselectとポリシー確認を追加しますが、エラーコードだけで確定はできません。
**「0件なら行が存在しない」**とも限りません。フィルター、環境、ユーザー、RLSのどれかが違えば、データベース上にある行でも現在のAPIリクエストでは返りません。権限を強めて一時的に読めたことだけで、クライアント側の設定を変えないでください。
**「.maybeSingle()にすれば直る」**も危険です。0件が正しい仕様なら選択肢になりますが、1件必須の処理で使うと欠落を検知できなくなる可能性があります。複数件が正しいなら配列を使い、重複が不正ならデータ整合性を調べます。
**「.limit(1)でエラーが消えたから解決」**ではありません。条件に一致する複数行のどれを採るかが未決なら、順序の違いで結果が変わります。業務上の一意性を確認し、必要なら別途制約やデータ修復を検討します。schema変更や大量データ修正は影響範囲を確認する別の判断です。
回帰テスト:0・1・複数と権限を分ける
一時的な使い捨てテストデータを使い、対象クエリについて0件、1件、複数件の応答を確認します。single()では1件だけが成功し、0件と複数件は呼び出し側が適切に処理することを確認します。maybeSingle()では0件と1件を正常系、複数件を不整合として扱えることを確認します。配列では0件を空一覧として、複数件を必要な順序と件数で扱えることを確認します。
RLSが関係する場合は、所有者、同一tenant内の別ユーザー、別tenantユーザーなど、アプリの権限モデルに合ったテスト主体を使います。見るべきなのは、各主体で意図したレコードだけが見えることです。ポリシーを一時的に外した状態で成功するかを試すのではなく、ポリシーを有効にしたままテストします。書き込み系は、対象行の更新可否と返却の有無を別々に検証します。
テスト後は、使い捨てデータを片付け、環境と認証主体、期待件数、実際の件数、最終的に選んだ返却形式を記録します。ログや画面に実データを貼らず、再現に必要な最小限の識別情報を使ってください。
切り分け結果をどう読むか
次のように結果を分類すると、単にエラーを消すのではなく次の作業が決まります。
表は横にスクロールできます
| 観測 | まず確認すること | 次の対応 |
|---|---|---|
| 同じユーザー・条件で0件、仕様では必須1件 | ID、初期化、環境、RLS、tenant条件 | 欠落の原因を直し、必須1件の契約は維持 |
| 同じユーザー・条件で0件、仕様上未設定が正常 | maybeSingle()の利用と空状態処理 | 未設定時の画面・作成導線を検証 |
| 同じユーザー・条件で複数件、仕様では1件 | フィルター、重複作成、制約の有無 | 重複の発生元を調べ、必要な修復を設計 |
| 複数件が仕様どおり | 単数形変換の使用箇所 | 配列処理へ合わせる |
| SELECTは行を返すがmutation後は返らない | mutationで返却を要求したか | 必要なら.select()を連結して最小列を検証 |
| 管理権限では行が見え、アプリ利用者では0件 | RLS、権限、JWT claim、ユーザー・tenant対応 | 最小権限でポリシーを検証。公開範囲を広げない |
これらは診断の方向であり、特定の結果から原因を断定する表ではありません。たとえば「管理権限では見える」だけでは、RLSが誤っているのか、アプリユーザーに意図どおり見せない設計なのかは分かりません。権限要件とポリシーの目的を一緒に確認します。
実装を変える前の最終判断
最後に、呼び出し元の要件を短く書きます。「設定は利用者ごとに必ず一つ」「初回は未作成でもよい」「検索結果は複数行」などです。その要件と現在のデータ制約、RLS、クエリ形、返却方式を同じレビューに並べてください。
要件がちょうど1件なら、single()を残して0件・複数件を別の回復可能なエラーとして扱います。0〜1件なら、maybeSingle()を検討し、0件のときの作成・空状態を定義します。複数件なら配列にし、ページング・並び順・空状態を実装します。どの場合も、RLSやフィルターを緩める前に、現在のユーザーが見てよい行とAPIが返してよい列を確認します。
PGRST116の解消は、例外を消すことではなく、データ件数・アクセス可視性・返却形式を仕様に揃えることです。原因を切り分けてから修正すれば、重複を隠す、必要な権限を弱める、変更が成功したのに失敗表示にする、といった二次障害を避けられます。
公式情報と確認範囲
以下は2026-10-15に確認したSupabase公式資料です。本文の説明はData APIのエラー定義、JavaScriptクライアントの単数・任意単数返却、RLS、および更新レスポンスの仕様に限定しています。プロジェクト固有のRLSポリシー、テーブル制約、クエリ、実データは未確認であり、この記事では特定のSupabase環境を実測したとは述べません。
・REST API Error Codes:https://supabase.com/docs/guides/api/rest/postgrest-error-codes(新しいタブで開く)
・JavaScript single():https://supabase.com/docs/reference/javascript/single(新しいタブで開く)
・JavaScript maybeSingle():https://supabase.com/docs/reference/javascript/maybesingle(新しいタブで開く)
・Row Level Security:https://supabase.com/docs/guides/database/postgres/row-level-security(新しいタブで開く)
・JavaScript update():https://supabase.com/docs/reference/javascript/update(新しいタブで開く)
