web-dev-qa-db-ja.com

PostgreSQLでレコード変数の列名を動的に渡す

PostgreSQLを使用すると、最初のレコードのテーブルの列値がレコード変数に格納されます。例:変数をrecordvarとします

recordvar.columnname

指定された列名の値を与えます。 columnameを変数で定義します。

var := columnname

columnnameの代わりに変数に置き換えた場合、つまりrecordvar.var、動作していません。

このような状況での進め方を教えてください。以下はサンプルコードです:

CREATE OR REPLACE FUNCTION getrowdata(id numeric, table_name character varying)
RETURNS SETOF void AS
$BODY$ 
DECLARE

srowdata record;
reqfield character varying;
value numeric;


BEGIN

RAISE NOTICE 'id: %',id; 
reqfield:= 'columnname';

EXECUTE 'select * from datas.'||table_name||' WHERE id = '||id into srowdata;

RAISE NOTICE 'srowdata: %',srowdata; 

RAISE NOTICE 'srowdatadata.columnname: %',srowdata.columnname;

value:= srowdata.reqfield;

RAISE NOTICE 'value: %',value;


END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
17
user2664380

このダミーテーブルの操作

_CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)
_

まず、私はあなたの例を単純化して消毒しました:

  • 質問に関係のないいくつかのノイズを削除しました。

  • _RETURNS SETOF void_はほとんど意味がありません。代わりに_RETURNS void_を使用します。

  • 簡単にするために、_character varying_の代わりにtextを使用します。

  • 動的SQLを使用する場合、SQLインジェクションから保護するためにhaveがあります。この場合、format()と_%I_を使用します。 他の方法があります

基本的な問題は、SQLは型と識別子に関して非常に厳格であるということです。 動的テーブル名と、レコードの動的フィールド名-元の例では、anonymousrecordです。 Pl/pgSQLは、これに対処するための十分な機能を備えていません。 Postgresは、匿名レコードの内部が何であるかを知りません。レコードを既知のタイプに割り当てた後でのみ、個々のフィールドを参照できます。
動的な名前のレコードのフィールドを設定しようとする、密接に関連する質問があります。
動的SQLを使用して複合変数フィールドの値を設定する方法

基本機能

_CREATE OR REPLACE FUNCTION getrowdata1(table_name text, id int)
  RETURNS void AS
$func$ 
DECLARE
   srowdata record;
   reqfield text := 'my_num';   -- assigning at declaration time for convenience
   value    numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT * FROM %I WHERE id = $1', table_name)
USING  id
INTO   srowdata;

RAISE NOTICE 'srowdata: %', srowdata;

RAISE NOTICE 'srowdatadata.my_num: %', srowdata.my_num;

/* This does not work, even with dynamic SQL
EXECUTE format('SELECT ($1).%I', reqfield)
USING srowdata
INTO value;

RAISE NOTICE 'value: %', value;
*/

END
$func$ LANGUAGE plpgsql;
_

コール:

_SELECT * from getrowdata1('foo', 1);
_

コメントされた部分は例外を発生させます:

レコードデータ型の列「my_num」を識別できませんでした:SELECT * from getrowdata(1、 'foo')

hstore

このために、追加のモジュール hstore をインストールする必要があります。データベースごとに1回:

_CREATE EXTENSION hstore;
_

それからすべてはこのように働くことができます:

_CREATE OR REPLACE FUNCTION getrowdata2(table_name text, id int)
  RETURNS void AS
$func$ 
DECLARE
   hstoredata hstore;
   reqfield   text := 'my_num';
   value      numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT hstore(t) FROM %I t WHERE id = $1', table_name)
USING  id
INTO   hstoredata;

RAISE NOTICE 'hstoredata: %', hstoredata;

RAISE NOTICE 'hstoredata.my_num: %', hstoredata -> 'my_num';

value := hstoredata -> reqfield;

RAISE NOTICE 'value: %', value;

END
$func$ LANGUAGE plpgsql;
_

コール:

_SELECT * from getrowdata2('foo', 1);
_

ポリモーフィック型

追加のモジュールをインストールせずに代替。

レコード変数に行全体を選択するため、定義ごとに明確に定義されたタイプがあります。これを使って。キーワードは polymorphic types です。

_CREATE OR REPLACE FUNCTION getrowdata3(_tbl anyelement, id int)
  RETURNS void AS
$func$ 
DECLARE
   reqfield text := 'my_num';
   value    numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT * FROM %s WHERE id = $1', pg_typeof(_tbl))
USING  id
INTO   _tbl;

RAISE NOTICE '_tbl: %', _tbl;

RAISE NOTICE '_tbl.my_num: %', _tbl.my_num;

EXECUTE 'SELECT ($1).' || reqfield   -- requfield must be SQLi-safe or escape
USING _tbl
INTO  value;

RAISE NOTICE 'value: %', value;

END
$func$ LANGUAGE plpgsql;
_

コール:

_SELECT * from getrowdata3(NULL::foo, 1);
_

-> SQLfiddle

21