Refactoriser un PL/pgSQL fonction pour renvoyer la sortie de diverses requêtes SELECT
j'ai écrit une fonction qui produit une requête PostgreSQL SELECT
bien formée sous forme de texte. Maintenant, je ne veux plus sortir de texte, mais en fait lancer la déclaration SELECT
générée contre la base de données et retourner le résultat - tout comme la requête elle-même le ferait.
Ce que j'ai jusqu'à présent:
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 := 'SELECTrnDatahora,' || sensors ||
'rnrnFROMrn' || type ||
'rnrnWHERErid=' || ||'rnrnORDER BY Datahora;';
RETURN result;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
sensors
contient la liste des noms de colonnes pour la table type
. Ceux qui sont déclarés et remplis dans le cadre de fonction. Finalement, ils détiennent des valeurs comme:
-
sensors
:'column1, column2, column3'
À l'exception deDatahora
(timestamp
), toutes les colonnes sont du typedouble precision
. -
type
:'myTable'
Peut être le nom d'une des quatre tables. Chacune a des colonnes différentes, à l'exception de la colonne communeDatahora
.
définition des tableaux sous-jacents .
la variable sensors
contiendra toutes les colonnes affichées ici pour le tableau correspondant dans type
. Par exemple: si type
est pcdmet
alors sensors
sera 'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'
les variables sont utilisées pour construire une déclaration SELECT
qui est stockée dans result
. Comme:
SELECT Datahora, column1, column2, column3
FROM myTable
WHERE id=20
ORDER BY Datahora;
en ce moment, ma fonction renvoie cette instruction comme text
. Je copie-colle et l'exécute dans pgAdmin ou via psql. Je veux automatiser ceci, exécuter la requête automatiquement et retourner le résultat. Comment puis-je le faire?
3 réponses
Dynamic SQL et RETURN
type
(j'ai gardé le meilleur pour la fin, continuez à lire!)
Vous voulez exécuter SQL dynamique . En principe, c'est simple en plpgsql avec l'aide de EXECUTE
. Vous ne pas besoin d'un curseur - en fait, la plupart du temps, vous êtes mieux sans curseurs explicites.
trouver des exemples sur SO avec une recherche .
Le problème: vous voulez renvoie les enregistrements de type non défini . Une fonction doit déclarer le type de retour avec le RETURNS
clause (ou avec les paramètres OUT
ou INOUT
). Dans votre cas, vous devriez revenir aux dossiers anonymes, parce que nombre , noms et types des colonnes retournées varient. Comme:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
cependant, cela n'est pas particulièrement utile. De cette façon, vous devez fournir une liste de définition de colonne avec chaque appel de la fonction. Comme:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
mais comment feriez-vous cela, alors que vous ne connaissez pas les colonnes à l'avance?
Vous pourriez recourir à un moins structuré types de données de document comme json
, jsonb
, hstore
ou xml
:
- comment conserver une table de données (ou une liste
>, ou un dictionnaire) dans une base de données?
mais pour les besoins de cette question supposons que vous voulez retourner des colonnes individuelles, correctement dactylographiées et nommées autant que possible.
solution Simple à type de retour fixe
la colonne datahora
semble être une donnée, je supposerai le type de données timestamp
et qu'il y a toujours deux autres colonnes avec des noms et des types de données variables.
Noms nous allons abandonner en faveur de noms génériques dans le type de retour.
Types nous allons abandonner, aussi, et tout cast à text
depuis chaque type de données peut être cast à 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 =
ORDER BY datahora'
USING _id;
END
$func$ LANGUAGE plpgsql;
comment ça marche?
-
les variables
_sensors
et_type
pourraient être des paramètres d'entrée à la place. -
Note la
RETURNS TABLE
clause. -
Remarque l'utilisation de
RETURN QUERY EXECUTE
. C'est l'une des plus élégante des façons de renvoie les lignes d'une requête dynamique. -
j'utilise un nom pour le paramètre de fonction, juste pour rendre la clause
USING
deRETURN QUERY EXECUTE
moins déroutante.dans la chaîne SQL ne se réfère pas au paramètre de fonction mais à la valeur passée avec la clause
USING
. (Il se trouve que les deux sontdans leur champ d'application respectif dans cet exemple simple.)
-
Note la valeur d'exemple pour
_sensors
: chaque colonne est coulée au typetext
. -
ce type de code est très vulnérable à injection SQL . J'utilise
quote_ident()
pour me protéger. Regrouper quelques noms de colonnes dans la variable_sensors
empêche l'utilisation dequote_ident()
(et est généralement une mauvaise idée!). S'assurer que pas mal de trucs peut-être quelques autres manière, par exemple en exécutant individuellement les noms de colonne parquote_ident()
à la place. Un paramètreVARIADIC
vient à l'esprit ...
plus simple avec PostgreSQL 9.1+
avec la version 9.1 ou une version ultérieure, vous pouvez utiliser format()
pour simplifier davantage:
RETURN QUERY EXECUTE format('
SELECT datahora, %s -- identifier passed as unescaped string
FROM %I -- assuming the name is provided by user
WHERE id =
ORDER BY datahora'
,_sensors, _type)
USING _id;
encore une fois, les noms de colonne individuels pourraient être échappés correctement et seraient la voie propre.
nombre Variable de colonnes partageant le même type
après la mise à jour de votre question, il semble que votre type de retour ait
- une variable nombre des colonnes
- mais toutes les colonnes de la même type
double precision
(aliasfloat8
)
comme nous devons définir le type RETURN
d'une fonction je recourt à un "1519400920 type" dans ce cas, qui peut contenir un nombre variable de valeurs. En outre, je renvoie un tableau avec les noms de colonne, de sorte que vous pourriez Parser les noms hors du résultat, aussi:
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() -- AS names
, ARRAY[%s] -- AS values
FROM %s
WHERE id =
ORDER BY datahora'
, _sensors, _type)
USING _sensors, _id;
END
$func$ LANGUAGE plpgsql;
divers types de tables complètes
si vous essayez réellement de retourner toutes les colonnes d'une table (par exemple l'une des tables à la page liée , puis utilisez cette solution simple et très puissante avec un polymorphic type :
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 =
ORDER BY datahora'
, pg_typeof(_tbl_type))
USING _id;
END
$func$ LANGUAGE plpgsql;
Appel:
SELECT * FROM data_of(NULL::pcdmet, 17);
remplacer pcdmet
dans l'appel avec tout autre nom de table.
comment ça marche?
-
anyelement
est un pseudo type de données, un type polymorphique, un placeholder pour tout non-array type de données. Toutes les occurrences deanyelement
dans la fonction évaluer au même type fourni à l'exécution. En fournissant une valeur d'un type défini comme argument à la fonction, nous définissons implicitement le type de retour. -
PostgreSQL définit automatiquement un type de ligne (un type de données composite) pour chaque table créée, il y a donc un type bien défini pour chaque table. Cela inclut les tables temporaires, ce qui est pratique pour une utilisation ad hoc.
-
tout type peut être
NULL
. Nous donnons donc une valeurNULL
, coulée au type table. -
maintenant la fonction retourne un type de ligne bien défini et nous pouvons utiliser
SELECT * FROM data_of(...)
pour décomposer la ligne et obtenir des colonnes individuelles. -
pg_typeof(_tbl_type)
renvoie le nom de la table comme type d'identificateur d'objetregtype
. Lorsqu'ils sont convertis automatiquement entext
, les identificateurs sont automatiquement cités en double et qualifiés de schéma si nécessaire. Par conséquent, l 'injection de SQL n' est pas possible. Cela peut même concerner les noms de tableaux qualifiés de schémas oùquote_ident()
échouerait .
vous voudrez probablement retourner un curseur . Essayez quelque chose comme ceci (Je ne l'ai pas essayé):
CREATE OR REPLACE FUNCTION data_of(integer)
RETURNS refcursor AS
$BODY$
DECLARE
--Declaring variables
ref refcursor;
BEGIN
-- make sure `sensors`, `type`, variable has valid value
OPEN ref FOR 'SELECT Datahora,' || sensors ||
' FROM ' || type ||
' WHERE nomepcd=' || ||' ORDER BY Datahora;';
RETURN ref;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
je suis désolé de dire mais votre question est très peu claire. Cependant vous trouverez ci-dessous une exemple comment créer et utiliser une fonction qui renvoie une variable de curseur. Espérons que cela aide !
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;