テキスト形式で整形式のPostgreSQL SELECT
クエリを出力する関数を書きました。これで、テキストを出力したくなくなりましたが、実際には、生成された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'
Datahora
(timestamp
)を除いて、すべての列のタイプはdouble precision
。
type
:'myTable'
4つのテーブルのいずれかの名前にすることができます。共通の列Datahora
を除いて、それぞれに異なる列があります。
変数sensors
には、ここに表示されるtype
の対応するテーブルのall列が保持されます。例:type
がpcdmet
の場合、sensors
は'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で実行します。これを自動化し、クエリを自動的に実行して結果を返したいのですが。どうやってやるの?
RETURN
タイプ(最後に最後まで保存しました。読み続けてください!)
実行したい動的SQL。原則として、それはplpgsqlでは EXECUTE
の助けを借りて簡単です。カーソルは必要ありません。実際、ほとんどの場合、明示的なカーソルがない方がよいでしょう。
SO検索あり の例を検索します。
遭遇する問題:まだ定義されていないタイプのレコードを返すが必要です。関数は、戻り値の型を RETURNS
句で(またはOUT
またはINOUT
パラメーターで)宣言する必要があります。あなたの場合、number、names、typesであるため、匿名レコードにフォールバックする必要があります返される列は異なります。お気に入り:
_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);
_
しかし、事前に列がわからない場合はどうすればよいでしょうか。json
、jsonb
、hstore
、xml
などの構造化されていないドキュメントデータタイプを使用することができます。
しかし、この質問の目的のために、可能な限り個々の正しく型付けされた名前付きの列を返したいとしましょう。
列datahora
は指定されているようです。データ型timestamp
を想定し、名前とデータ型が異なる2つ以上の列が常に存在すると仮定します。
Names戻り値の型では総称名を使用することをやめます。
Typesも放棄して、すべてをtext
にキャストします。これは、everyデータ型がtext
。
_CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text) 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$ LANGUAGE plpgsql;
_
代わりに、変数__sensors
_および__type
_を入力パラメーターにすることができます。
_RETURNS TABLE
_ 句に注意してください。
_RETURN QUERY EXECUTE
_ の使用に注意してください。これは、動的クエリから行を返す最も洗練された方法の1つです。
_RETURN QUERY EXECUTE
_のUSING
句をわかりやすくするために、関数パラメーターの名前を使用しています。 SQL文字列内の_$1
_は、関数パラメーターではなく、USING
句で渡された値を参照します。 (この単純な例では、どちらもたまたまそれぞれのスコープで_$1
_です。)
__sensors
_の値の例に注意してください。各列はtext
型にキャストされます。
この種のコードは SQLインジェクション に対して非常に脆弱です。私は quote_ident()
を使用してそれから保護しています。変数__sensors
_にいくつかの列名をまとめると、quote_ident()
を使用できなくなります(通常、これは悪い考えです!)。たとえば、代わりにquote_ident()
を使用して列名を個別に実行するなど、他の方法で問題が発生しないことを確認してください。 VARIADIC
パラメータが思い浮かびます...
バージョン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
_)関数のRETURN
型を定義する必要があるので、この場合、可変数の値を保持できるARRAY
型に頼ります。さらに、列名を含む配列を返すので、結果から名前を解析することもできます。
_CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[] ) 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$ LANGUAGE plpgsql;
_
実際にテーブルのすべての列を返そうとしている場合(たとえば、リンクされたページの テーブルの1つ の場合)、このシンプルで非常に強力なソリューションを-で使用します 多相型 :
_CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement 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$ LANGUAGE plpgsql;
_
コール:
_SELECT * FROM data_of(NULL::pcdmet, 17);
_
呼び出しのpcdmet
を他のテーブル名に置き換えます。
anyelement
は、疑似データ型、ポリモーフィック型、非配列データ型のプレースホルダーです。関数内のすべてのanyelement
は、実行時に提供される同じ型に評価されます。定義された型の値を関数の引数として指定することで、戻り値の型を暗黙的に定義します。
PostgreSQLは、作成されたすべてのテーブルの行タイプ(複合データタイプ)を自動的に定義するため、すべてのテーブルに明確に定義されたタイプがあります。これには、一時的なテーブルが含まれており、臨時の使用に便利です。
どの型もNULL
にすることができます。したがって、NULL
値を渡して、テーブルタイプにキャストします。
これで、関数は明確に定義された行タイプを返し、SELECT * FROM data_of(...)
を使用して行を分解し、個々の列を取得できます。
pg_typeof(_tbl_type)
テーブルの名前を object identifier type regtype
として返します。自動的にtext
に変換される場合、識別子は必要に応じて自動的に二重引用符で囲まれ、スキーマ修飾されますになります。したがって、SQLインジェクションは不可能です。これは、スキーマ修飾されたテーブル名 quote_ident()
が失敗する場合 にも対応できます。
おそらく 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;
申し訳ありませんが、質問は非常に不明確です。ただし、以下に、カーソル変数を返す関数を作成して使用する方法の自己完結型の例を示します。それが役に立てば幸い !
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;
# copy paste me into bash Shell directly
clear; IFS='' read -r -d '' sql_code << 'EOF_SQL_CODE'
CREATE OR REPLACE FUNCTION func_get_all_users_roles()
-- define the return type of the result set as table
-- those datatypes must match the ones in the src
RETURNS TABLE (
id bigint
, email varchar(200)
, password varchar(200)
, roles varchar(100)) AS
$func$
BEGIN
RETURN QUERY
-- start the select clause
SELECT users.id, users.email, users.password, roles.name as roles
FROM user_roles
LEFT JOIN roles ON (roles.guid = user_roles.roles_guid)
LEFT JOIN users ON (users.guid = user_roles.users_guid)
-- stop the select clause
;
END
$func$ LANGUAGE plpgsql;
EOF_SQL_CODE
# create the function
psql -d db_name -c "$sql_code";
# call the function
psql -d db_name -c "select * from func_get_all_users_roles() "