web-dev-qa-db-ja.com

PL / pgSQL関数から不明な列を持つ動的テーブルを返します

infowindowフィールドが存在するかどうかを特定のテーブルでチェックする関数を作成する必要があります。存在する場合、関数はselect * from tableを返す必要がありますが、存在しない場合は、追加のidフィールドを返す必要があります。

CREATE OR REPLACE FUNCTION getxo_ocx_cincu_preparar_infowindow(
                                              guretabla character varying)
  RETURNS TABLE AS
$BODY$ 
DECLARE
    tabla ALIAS FOR $1;

BEGIN

IF  EXISTS (SELECT 1
   FROM   pg_namespace n
   JOIN   pg_class     c ON c.relnamespace = n.oid
   JOIN   pg_attribute a ON a.attrelid = c.oid 
   WHERE  n.nspname = current_schema()  -- default to current schema
   AND    c.relname = tabla
   AND    a.attname = 'infowindow'
   AND    NOT a.attisdropped)
THEN
    RETURN QUERY EXECUTE 'SELECT * from ' ||tabla ;
ELSE
    RETURN QUERY EXECUTE 'SELECT *, ID:' || id::text ||' as infowindow
                                   from ' ||tabla ;
END IF;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

RETURNS SETOF RECORDSを使用する場合、関数を選択するときに、列を指定する必要がありますが、これはわかりません。また、RETURNS TABLEを使用する場合は、フィールドも指定する必要があるため、その方法がわかりません。

14
Egidi

SQLは戻り値の型を知る必要があるため、これを解決するのは困難です呼び出し時
また、plpgsql関数には明確に定義された戻り値の型が必要です。

匿名レコードを返すことを選択した場合、定義したもの、つまり匿名レコードを取得します。 Postgresは何が入っているのかわかりません。したがって、タイプを分解するには、列定義リストは必須です。

正確な要件に応じて、さまざまな回避策があります。戻り値の型を知る方法がある場合呼び出し時、この回答の最後の章で概説されているようにポリモーフィック型をお勧めします(「さまざまな完全なテーブル型」):
PL/pgSQL関数をリファクタリングして、さまざまなSELECTクエリの出力を返します

しかし、それは戻り値の型に別の列を追加することをカバーしていません関数内の実行時。それは不可能です。私はアプローチ全体を再考します

あなたの現在のアプローチに関して、私が考えることができる最も近いものは、シングル内の2回目の呼び出しでクエリする一時テーブル( またはカーソル )です。トランザクション

コードにその他の問題がいくつかあります。以下の注を参照してください。

コンセプトの証明

_CREATE OR REPLACE FUNCTION f_tbl_plus_infowindow (_tbl regclass) -- regclass!
  RETURNS void AS  -- no direct return type
$func$
DECLARE
   -- appending _tmp for temp table
   _tmp text := quote_ident(_tbl::text || '_tmp');
BEGIN

-- Create temp table only for duration of transaction
EXECUTE format(
   'CREATE TEMP TABLE %s ON COMMIT DROP AS TABLE %s LIMIT 0', _tmp, _tbl);

IF EXISTS (
   SELECT 1
   FROM   pg_attribute a
   WHERE  a.attrelid = _tbl
   AND    a.attname  = 'infowindow'
   AND    a.attisdropped = FALSE)
THEN
   EXECUTE format('INSERT INTO %s SELECT * FROM %s', _tmp, _tbl);
ELSE
  -- This is assuming a NOT NULL column named "id"!
   EXECUTE format($x$
      ALTER  TABLE %1$s ADD COLUMN infowindow text;
      INSERT INTO %1$s
      SELECT *, 'ID: ' || id::text
      FROM   %2$s $x$
     ,_tmp, _tbl);
END IF;

END
$func$ LANGUAGE plpgsql;
_

呼び出しは単一のトランザクションで行う必要があります。クライアントによっては、明示的なトランザクションを開始する必要がある場合があります。

_BEGIN;
SELECT f_tbl_plus_infowindow ('tbl');
SELECT * FROM tbl_tmp;  -- do something with the returned rows
ROLLBACK;               -- or COMMIT, does not matter here
_

SQLフィドル。

または、セッションの間、一時テーブルを存続させることもできます。ただし、繰り返し呼び出される名前の衝突には注意してください。

ノート

  • 古いALIASコマンド の代わりにパラメーター名を使用してください。

  • 現在のスキーマを実際に「デフォルト」にするには、表示するより単純なクエリを使用します。 regclassを使用すると、トリックが自動的に実行されます。詳細:

    さらに、これにより、元のコードの非標準(または悪意を持って不正な形式)のテーブル名からの構文エラーやSQLインジェクションの可能性も回避されます。

  • ELSE句のコードはまったく機能しません。

  • _TABLE tbl;_は基本的に_SELECT * FROM tbl;_の略です。

  • マニュアルのformat()の詳細。

23