PostgreSQL『ERROR: view cannot return tuples of different sizes』の原因と対処
- 作成日 2025.09.30
- 更新日 2025.10.06
- その他
ビューが返す1行(タプル)の“列数(物理サイズ)”が一定にならないと検出されたときに発生する。典型は、ビューのON SELECTルールが複数あって出力列が一致していない、UNION/関数/複合型の扱いで分岐ごとに列数がズレる、あるいはビューの再定義や下位オブジェクト変更で列構成が不整合になったケース。症状別に切り分け、再現→修正の実例、診断SQL、再発防止のベストプラクティスまで。
目次
- 1. エラーメッセージと意味
- 2. 発生条件の早見表
- 3. まず確認:ビューの定義とルール/依存関係
- 4. 再現1:ON SELECTルールが複数で列数が違う → エラー
- 5. 対処1:ON SELECTルールを1本化する or 列数を揃える
- 6. 再現2:UNION/UNION ALLの列不一致(NULL/CASTで揃える)
- 7. 再現3:record/複合型の混在(whole-row/ROW(…)を明示的に合わせる)
- 8. 再現4:SETOF record/外部関数が状況で列構成を変える
- 9. ビュー再定義・下位変更が絡む場合の安全手順
- 10. 診断SQL:どこで列が揃っていないかを見抜く
- 11. 安全な書き方テンプレ(列数を“常に”揃える)
- 12. 運用チェックリスト
- 13. 最小再現→解消の通し例(ルール複数→単一化)
- 14. まとめ:ビューは常に“同じ姿の1行”を返すように固定する
エラーメッセージと意味
ERROR: view cannot return tuples of different sizes・ビューが返すタプルの“列数(バイト長ではなく列の個数)”が文脈によって異なると判断された
・ビューは常に「列名・順序・型」が固定の一様な行を返す必要がある
発生条件の早見表
□ ビューに複数の ON SELECT ルールを付け、各ルールのSELECTが異なる列数を返す
□ ビュー定義内の UNION/UNION ALL/CASE で分岐ごとに SELECT 列数が一致していない
□ 関数(SETOF record/複合型)から返る列構成が分岐や呼び方で変わってしまう
□ whole-row 参照(SELECT t)や異なる複合型(ROW(...))を混在させて合成している
□ 下位テーブルや関数の変更後にビューを再定義した結果、依存関係で列不一致が露呈まず確認:ビューの定義とルール/依存関係
-- ビューのSQLを確認
SELECT pg_get_viewdef('public.myview'::regclass, true);
-- 当該ビューに付与されたルール(ON SELECT ルールが複数ないか)
SELECT * FROM pg_rules WHERE schemaname='public' AND tablename='myview';
-- 列の定義(ビューの固定スキーマを把握)
SELECT column_name, ordinal_position, data_type
FROM information_schema.columns
WHERE table_schema='public' AND table_name='myview'
ORDER BY ordinal_position;
-- 依存しているオブジェクト(下位の変更有無を疑う)
SELECT pg_describe_object(classid,objid,objsubid) AS depends_on
FROM pg_depend
WHERE refobjid='public.myview'::regclass
ORDER BY 1;再現1:ON SELECTルールが複数で列数が違う → エラー
-- 1) 普通のビューを作成(2列)
CREATE OR REPLACE VIEW v AS SELECT 1 AS a, 2 AS b;
-- 2) 誤って、1列しか返さない追加のON SELECTルールを作成
CREATE RULE v_extra AS ON SELECT TO v DO INSTEAD SELECT 3 AS a;
-- 3) 実行すると…
SELECT * FROM v;
-- ERROR: view cannot return tuples of different sizes対処1:ON SELECTルールを1本化する or 列数を揃える
-- 余計なルールを削除(推奨)
DROP RULE v_extra ON v;
-- どうしても複数ルールで構成したい場合は、各ルールのSELECTを同じ列数・型に揃える
-- 例:不足列は NULL::型 で合わせる
CREATE RULE v_extra AS ON SELECT TO v DO INSTEAD
SELECT 3 AS a, NULL::int AS b; -- 列数を合わせる・基本方針:ビューは単一のSELECT(CREATE VIEW … AS …)に集約する
再現2:UNION/UNION ALLの列不一致(NULL/CASTで揃える)
-- NG:UNION ALLの片側だけ列が1本少ない
CREATE OR REPLACE VIEW v_union AS
SELECT id, name FROM t1
UNION ALL
SELECT id FROM t2; -- ← nameが無い
-- OK:不足列はNULLで埋め、型もCASTで揃える
CREATE OR REPLACE VIEW v_union AS
SELECT id, name FROM t1
UNION ALL
SELECT id, NULL::text AS name FROM t2;・UNION/UNION ALLは作成時に列数相違で弾かれることが多いが、関数やrecord経由だと見落としやすい
再現3:record/複合型の混在(whole-row/ROW(…)を明示的に合わせる)
-- 例:テーブルt_a(id int,name text) と t_b(id int) を合成したい
-- NG:whole-rowを混在させると、複合型の列数が違って不整合になりやすい
CREATE OR REPLACE VIEW v_comp AS
SELECT t_a FROM t_a
UNION ALL
SELECT t_b FROM t_b; -- t_bにはname列が無い → 不一致
-- OK1:列を明示列挙して揃える(推奨)
CREATE OR REPLACE VIEW v_comp AS
SELECT id, name FROM t_a
UNION ALL
SELECT id, NULL::text AS name FROM t_b;
-- OK2:共通の複合型を作り、それに明示キャストして統一
CREATE TYPE common_t AS (id int, name text);
CREATE OR REPLACE VIEW v_comp AS
SELECT ROW(id, name)::common_t AS r FROM t_a
UNION ALL
SELECT ROW(id, NULL::text)::common_t AS r FROM t_b;・whole-row(SELECT t)やROW(…)は“1列の複合型”を返す。異なる複合型を混ぜない
再現4:SETOF record/外部関数が状況で列構成を変える
-- NG:呼び先関数の返す列が状況で変わる/推論不能
CREATE OR REPLACE VIEW v_func AS
SELECT * FROM some_func(); -- record推論に失敗→分岐で列構成がズレる
-- OK:列定義リストを固定(FROM 関数 ... 列定義)か、専用の型を返す関数にする
CREATE TYPE stable_t AS (a int, b text);
CREATE OR REPLACE FUNCTION some_func_stable(...) RETURNS SETOF stable_t AS $$ ... $$ LANGUAGE sql;
CREATE OR REPLACE VIEW v_func AS
SELECT * FROM some_func_stable(...);
-- もしくは record の場合に列定義を明示
CREATE OR REPLACE VIEW v_func AS
SELECT f.a, f.b
FROM some_func() AS f(a int, b text);・dblink/外部データソースでも同様。常に固定スキーマで受ける
ビュー再定義・下位変更が絡む場合の安全手順
-- 1) 依存関係を洗い出し、順序を決める
SELECT pg_describe_object(classid,objid,objsubid) AS depends_on
FROM pg_depend
WHERE refobjid='public.myview'::regclass;
-- 2) 必要なら依存ビューを一時的にDROP ... CASCADEし、最後に再作成
-- (DDLはスクリプト化して“元の列構成”を常に満たす)
-- 3) CREATE OR REPLACE VIEW は「列名・順序・型」を変えないのが大原則
-- 変更が必要なら新しいビュー名で作って切替(旧ビューは互換ラッパにする)・ビューの列構成を変えると別のエラー(“列の変更不可”)に発展しがち。互換レイヤーで段階移行
診断SQL:どこで列が揃っていないかを見抜く
-- ビューSQLの生確認(SELECT * や record/ROW(...)の有無を見る)
SELECT pg_get_viewdef('public.myview'::regclass, true);
-- ビューに複数のON SELECTルールがあるか(通常は1本だけ)
SELECT rulename, ev_type, ev_enabled, ev_action
FROM pg_rules
WHERE schemaname='public' AND tablename='myview';
-- ビュー列の確定スキーマ(これに常に一致させる)
SELECT ordinal_position, column_name, data_type
FROM information_schema.columns
WHERE table_schema='public' AND table_name='myview'
ORDER BY ordinal_position;安全な書き方テンプレ(列数を“常に”揃える)
-- 1) SELECT * を避け、列を明示
CREATE OR REPLACE VIEW v_safe AS
SELECT id, name, created_at FROM t;
-- 2) UNION/分岐は不足列を NULL::型 で合わせ、型もCASTで統一
SELECT id, name FROM t1
UNION ALL
SELECT id, NULL::text AS name FROM t2;
-- 3) 関数は固定の複合型を返す or FROM関数で列定義を明示
SELECT f.a, f.b FROM func() AS f(a int, b text);
-- 4) whole-row/ROW(...)を使うなら、共通の複合型にキャストして統一
SELECT ROW(id, name)::common_t FROM t1
UNION ALL
SELECT ROW(id, NULL::text)::common_t FROM t2;運用チェックリスト
□ ビューに複数のON SELECTルールが付いていないか(pg_rulesで確認)
□ ビューのSELECTは常に同じ列数・型・順序を返すか(NULL/CASTで揃える)
□ SELECT * を避け、列を明示(下位の列追加で意図せずズレないように)
□ record/複合型は共通の型を定義して統一 or 列定義リストで受ける
□ ビューの互換性を壊す変更は“新ビュー名→切替→旧ラッパ廃止”で段階移行
□ 依存関係(pg_depend)を見て、再定義の順序と影響範囲を把握最小再現→解消の通し例(ルール複数→単一化)
-- 再現
DROP VIEW IF EXISTS v;
CREATE VIEW v AS SELECT 1 AS a, 2 AS b;
CREATE RULE v_extra AS ON SELECT TO v DO INSTEAD SELECT 3 AS a; -- 1列だけ
SELECT * FROM v;
-- ERROR: view cannot return tuples of different sizes
-- 解消:余計なルールを削除(または列数を合わせる)
DROP RULE v_extra ON v;
SELECT * FROM v; -- 正常に2列が返る
-- 別案:どうしても複数ルールなら列数を合わせる
CREATE RULE v_extra AS ON SELECT TO v DO INSTEAD SELECT 3 AS a, NULL::int AS b;
SELECT * FROM v; -- OK(2列で統一)まとめ:ビューは常に“同じ姿の1行”を返すように固定する
・列数・型・順序は常に一定:不足は NULL::型、型は CAST で揃える
・ON SELECTルールは1本に集約(複数なら全ルールで列数・型を完全一致)
・record/複合型/whole-rowは共通型に寄せるか、列定義を明示
・下位変更や再定義は依存関係を確認し、互換ラッパで段階移行-
前の記事
PostgreSQL『more than one row returned by a subquery used as an expression』の原因と対処 2025.09.29
-
次の記事
PostgreSQL『duplicate null key value violates unique constraint』の原因と対処 2025.10.02
コメントを書く