I wrote a function that outputs a PostgreSQL SELECT
query well formed in text form. Now I don't want to output a text anymore, but actually run the generated SELECT
statement against the database and return the result - just like the query itself would.
What I have so far:
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
holds the list of column names for the table type
. Those are declared and filled in the course of the function. Eventually, they hold values like:
sensors
:'column1, column2, column3'
Except forDatahora
(timestamp
) all columns are of typedouble precision
.type
:'myTable'
Can be the name of one of four tables. Each has different columns, except for the common columnDatahora
.
Definition of the underlying tables.
The variable sensors
will hold all columns displayed here for the corresponding table in type
. For example: If type
is pcdmet
then sensors
will be 'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'
The variables are used to build a SELECT
statement that is stored in result
. Like:
SELECT Datahora, column1, column2, column3
FROM myTable
WHERE id=20
ORDER BY Datahora;
Right now, my function returns this statement as text
. I copy-paste and execute it in pgAdmin or via psql. I want to automate this, run the query automatically and return the result. How can I do that?
I'm sorry to say but your question is very unclear. However below you'll find a self contained example how to create and use a function that returns a cursor variable. Hope it helps !
Dynamic SQL and
RETURN
type(I saved the best for last, keep reading!)
You want to execute dynamic SQL. In principal, that's simple in plpgsql with the help of
EXECUTE
. You don't need a cursor - in fact, most of the time you are better off without explicit cursors.Find examples on SO with a search.
The problem you run into: you want to return records of yet undefined type. A function needs to declare the return type with the
RETURNS
clause (or withOUT
orINOUT
parameters). In your case you would have to fall back to anonymous records, because number, names and types of returned columns vary. Like:However, this is not particularly useful. This way you'd have to provide a column definition list with every call of the function. Like:
But how would you even do this, when you don't know the columns beforehand?
You could resort to a less structured document data types like
json
,jsonb
,hstore
orxml
:But for the purpose of this question let's assume you want to return individual, correctly typed and named columns as much as possible.
Simple solution with fixed return type
The column
datahora
seems to be a given, I'll assume data typetimestamp
and that there are always two more columns with varying name and data type.Names we'll abandon in favor of generic names in the return type.
Types we'll abandon, too, and cast all to
text
since every data type can be cast totext
.How does this work?
The variables
_sensors
and_type
could be input parameters instead.Note the
RETURNS TABLE
clause.Note the use of
RETURN QUERY EXECUTE
. That is one of the more elegant ways to return rows from a dynamic query.I use a name for the function parameter, just to make the
USING
clause ofRETURN QUERY EXECUTE
less confusing.$1
in the SQL-string does not refer to the function parameter but to the value passed with theUSING
clause. (Both happen to be$1
in their respective scope in this simple example.)Note the example value for
_sensors
: each column is cast to typetext
.This kind of code is very vulnerable to SQL injection. I use
quote_ident()
to protect against it. Lumping together a couple of column names in the variable_sensors
prevents the use ofquote_ident()
(and is typically a bad idea!). Ensure that no bad stuff can be in there some other way, for instance by individually running the column names throughquote_ident()
instead. AVARIADIC
parameter comes to mind ...Simpler with PostgreSQL 9.1+
With version 9.1 or later you can use
format()
to further simplify:Again, individual column names could be escaped properly and would be the clean way.
Variable number of columns sharing the same type
After your question updates it looks like your return type has
double precision
(aliasfloat8
)As we have to define the
RETURN
type of a function I resort to anARRAY
type in this case, which can hold a variable number of values. Additionally, I return an array with column names, so you could parse the names out of the result, too:Various complete table types
If you are actually trying to return all columns of a table (for instance one of the tables at the linked page, then use this simple, very powerful solution with a polymorphic type:
Call:
Replace
pcdmet
in the call with any other table name.How does this work?
anyelement
is a pseudo data type, a polymorphic type, a placeholder for any non-array data type. All occurrences ofanyelement
in the function evaluate to the same type provided at run time. By supplying a value of a defined type as argument to the function, we implicitly define the return type.PostgreSQL automatically defines a row type (a composite data type) for every table created, so there is a well defined type for every table. This includes temporary tables, which is convenient for ad-hoc use.
Any type can be
NULL
. So we hand in aNULL
value, cast to the table type.Now the function returns a well-defined row type and we can use
SELECT * FROM data_of(...)
to decompose the row and get individual columns.pg_typeof(_tbl_type)
returns the name of the table as object identifier typeregtype
. When automatically converted totext
, identifiers are automatically double-quoted and schema-qualified if needed. Therefore, SQL injection is not a possible. This can even deal with schema-qualified table-names wherequote_ident()
would fail.You'll probably want to return a cursor. Try something like this (I haven't tried it):