38

SELECT整形式の PostgreSQL クエリをテキスト形式で出力する関数を作成しました。もうテキストを出力したくはありませんが、実際には生成されたSELECTステートメントをデータベースに対して実行し、クエリ自体と同じように結果を返します。

私がこれまでに持っているもの:

CREATE OR REPLACE FUNCTION data_of(integer)
  RETURNS text AS
$BODY$
DECLARE
   sensors varchar(100);   -- holds list of column names
   type    varchar(100);   -- holds name of table
   result  text;           -- holds SQL query
       -- declare more variables

BEGIN
      -- do some crazy stuff

      result := 'SELECT\r\nDatahora,' || sensors ||
      '\r\n\r\nFROM\r\n' || type ||
      '\r\n\r\nWHERE\r\id=' || $1 ||'\r\n\r\nORDER BY Datahora;';

      RETURN result;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;

sensorsテーブルの列名のリストを保持しますtype。それらは、関数の過程で宣言され、埋められます。最終的に、それらは次のような値を保持します。

  • sensors: ( )'column1, column2, column3'
    を除いて、すべての列は型です。Datahoratimestampdouble precision

  • type: 'myTable'
    4 つのテーブルのいずれかの名前にすることができます。共通の列を除いて、それぞれに異なる列がありますDatahora

基礎となるテーブルの定義

この変数は、 の対応するテーブルに対してここに表示されるすべてのsensorsを保持します。例: _ _typetypepcdmetsensors'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'

変数は、SELECTに格納されるステートメントを作成するために使用されresultます。お気に入り:

SELECT Datahora, column1, column2, column3
FROM   myTable
WHERE  id=20
ORDER  BY Datahora;

現在、私の関数はこのステートメントを として返しますtext。pgAdminまたはpsqlを介してコピーアンドペーストして実行します。これを自動化し、クエリを自動的に実行して結果を返したいです。どうやってやるの?

4

4 に答える 4

92

動的 SQL とRETURN

(最後に最良のものを保存しました。読み続けてください!)動的 SQL
を実行したいとします。原則として、それは plpgsql の助けを借りて簡単です。カーソルは必要ありません。実際、ほとんどの場合、明示カーソルを使用しないほうがよいでしょう。EXECUTE

遭遇する問題:まだ定義されていない type のレコードを返したい。関数は、その戻り値の型をRETURNS句で (またはOUTまたはINOUTパラメータで) 宣言する必要があります。あなたの場合、返される列の名前、およびタイプが異なるため、匿名レコードにフォールバックする必要があります。お気に入り:

CREATE FUNCTION data_of(integer)
  RETURNS SETOF record AS ...

ただし、これは特に有用ではありません。すべての呼び出しで列定義リストを提供する必要があります。お気に入り:

SELECT * FROM data_of(17)
AS foo (colum_name1 integer
      , colum_name2 text
      , colum_name3 real);

しかし、事前に列がわからない場合、どうやってこれを行うのでしょうか? 、、または
などのあまり構造化されていないドキュメント データ型を使用できます。見る:jsonjsonbhstorexml

ただし、この質問の目的のために、正しく型付けされ、名前が付けられた個々の列を可能な限り返したいと仮定しましょう。

固定リターン型のシンプルなソリューション

列は指定されているようです。データ型を想定し、名前とデータ型が異なる列が常に 2 つあるとdatahora仮定します。timestamp

戻り値の型の一般的な名前を優先して放棄する名前。すべてのデータ
を にキャストできるため、型も放棄し、すべてをtextにキャストします。text

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, col2 text, col3 text)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1::text, col2::text';  -- cast each col to text
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE '
      SELECT datahora, ' || _sensors || '
      FROM   ' || quote_ident(_type) || '
      WHERE  id = $1
      ORDER  BY datahora'
   USING  _id;
END
$func$;

代わりに、変数_sensors_typeを入力パラメーターにすることができます。

RETURNS TABLE句に注意してください。

の使用に注意してくださいRETURN QUERY EXECUTE。これは、動的クエリから行を返すより洗練された方法の 1 つです。

USING節をRETURN QUERY EXECUTE混乱させないようにするため、関数パラメーターに名前を使用します。$1SQL 文字列内の は、関数パラメーターではなく、USING句で渡された値を参照します。(この単純な例では、どちらも$1それぞれのスコープ内にあります。)

の値の例に注意してください_sensors。各列は type にキャストされtextます。

この種のコードは、 SQL インジェクションに対して非常に脆弱です。私はquote_ident()それから保護するために使用します。変数内のいくつかの列名をひとまとめにすると_sensors、 の使用が妨げられますquote_ident()(通常は悪い考えです!)。quote_ident()たとえば、代わりに列名を個別に実行するなど、他の方法で悪いものがそこにないことを確認してください。VARIADICパラメータが思い浮かびます...

PostgreSQL 9.1 からよりシンプルに

バージョン 9.1 以降ではformat()、さらに単純化するために使用できます。

RETURN QUERY EXECUTE format('
   SELECT datahora, %s  -- identifier passed as unescaped string
   FROM   %I            -- assuming the name is provided by user
   WHERE  id = $1
   ORDER  BY datahora'
  ,_sensors, _type)
USING  _id;

繰り返しますが、個々の列名は適切にエスケープでき、クリーンな方法です。

同じ型を共有する可変数の列

質問が更新された後、戻り値の型が

  • 可変の列
  • ただし、すべて同じタイプ の列double precision(エイリアスfloat8)

この場合、型を使用しARRAYて可変数の値をネストします。さらに、列名を含む配列を返します。

CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, names text[], values float8[])
  LANGUAGE plpgsql AS
$func$
DECLARE
   _sensors text := 'col1, col2, col3';  -- plain list of column names
   _type    text := 'foo';
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT datahora
           , string_to_array($1)  -- AS names
           , ARRAY[%s]            -- AS values
      FROM   %s
      WHERE  id = $2
      ORDER  BY datahora'
    , _sensors, _type)
   USING  _sensors, _id;
END
$func$;

さまざまな完全なテーブル タイプ

テーブルのすべての列を実際に返すには、多相型を使用したシンプルで強力なソリューションがあります。

CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format('
      SELECT *
      FROM   %s  -- pg_typeof returns regtype, quoted automatically
      WHERE  id = $1
      ORDER  BY datahora'
    , pg_typeof(_tbl_type))
   USING  _id;
END
$func$;

電話 (重要!):

SELECT * FROM data_of(NULL::pcdmet, 17);

pcdmet呼び出しで他のテーブル名に置き換えます。

これはどのように作動しますか?

anyelement疑似データ型、ポリモーフィック型、非配列データ型のプレースホルダーです。関数内の のすべての出現はanyelement、実行時に提供されるのと同じ型に評価されます。定義された型の値を関数の引数として提供することにより、戻り値の型を暗黙的に定義します。

PostgreSQL は、作成されたすべてのテーブルの行の型 (複合データ型) を自動的に定義するため、すべてのテーブルに適切に定義された型があります。これには一時テーブルが含まれており、アドホックな使用に便利です。

任意の型にすることができますNULL。値を渡しNULL、テーブル タイプにキャストします: NULL::pcdmet

これで、関数は明確に定義された行タイプを返し、SELECT * FROM data_of()行を分解して個々の列を取得するために使用できます。

pg_typeof(_tbl_type)テーブルの名前をオブジェクト識別子の型regtypeとして返します。に自動的に変換されるとtext、識別子は自動的に二重引用符で囲まれ、必要に応じてスキーマ修飾され、SQL インジェクションから自動的に防御されます。これは、失敗するスキーマ修飾されたテーブル名を処理することさえできますquote_ident()。見る:

于 2012-08-01T01:51:13.627 に答える
4

おそらくcursorを返したいでしょう。このようなことを試してください(私は試していません):

CREATE OR REPLACE FUNCTION data_of(integer)
  RETURNS refcursor AS
$BODY$
DECLARE
      --Declaring variables
      ref refcursor;
BEGIN
      -- make sure `sensors`, `type`, $1 variable has valid value
      OPEN ref FOR 'SELECT Datahora,' || sensors ||
      ' FROM ' || type ||
      ' WHERE nomepcd=' || $1 ||' ORDER BY Datahora;';
      RETURN ref;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
于 2012-07-31T12:52:40.607 に答える
1

申し訳ありませんが、あなたの質問は非常に不明確です。ただし、以下に、カーソル変数を返す関数を作成して使用する方法の自己完結型の例を示します。それが役に立てば幸い !

begin;

create table test (id serial, data1 text, data2 text);

insert into test(data1, data2) values('one', 'un');
insert into test(data1, data2) values('two', 'deux');
insert into test(data1, data2) values('three', 'trois');

create function generate_query(query_name refcursor, columns text[])
returns refcursor 
as $$
begin
  open query_name for execute 
    'select id, ' || array_to_string(columns, ',') || ' from test order by id';
  return query_name;
end;
$$ language plpgsql;

select generate_query('english', array['data1']);
fetch all in english;

select generate_query('french', array['data2']);
fetch all in french;
move absolute 0 from french; -- do it again !
fetch all in french;

select generate_query('all_langs', array['data1','data2']);
fetch all in all_langs;

-- this will raise in runtime as there is no data3 column in the test table
select generate_query('broken', array['data3']);

rollback;
于 2012-07-31T19:01:35.640 に答える