Return set of records with unknown table_name - sql

I want to return a row from a table with this function (I don't know the name of the table, it's random)
CREATE FUNCTION foo( text ) RETURNS setof record AS $$
DECLARE
table_name ALIAS FOR $1;
BEGIN
SELECT * from table_name ;
END
$$ LANGUAGE plpgsql;
and then I want to do this:
select col1,col2 from foo('bar');
Any ideas?

SQL demands to know the return type at call time. And functions require you to define a return type as well. What you are after is not trivial.
If you don't know the return type at call time, you are basically out of luck. You cannot solve the problem with a single function call.
If you know the type at call time, there is an option with polymorphic types and dynamic SQL with EXECUTE:
CREATE OR REPLACE FUNCTION f_data_of_table(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_tbl_type);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM f_data_of_table(NULL::my_table_name);
Details in this related answer:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Be wary of SQL injection:
Table name as a PostgreSQL function parameter
Only makes sense if you do more than just SELECT * FROM tbl, or you'd simply use the SQL command.
Aside:
Do not use ALIAS to attach names to parameter values. That's outdated and discouraged. Use named parameters instead.

Related

Infer row type from table in postgresql

My application uses multiple schemas to partition tenants across the database to improve performance. I am trying to create a plpgsql function that will give me an arbitrary result set based on the union of all application schemas given a table. Here is what I have so far (inspired by this blog post):
CREATE OR REPLACE FUNCTION app_union(tbl text) RETURNS SETOF RECORD AS $$
DECLARE
schema RECORD;
sql TEXT := '';
BEGIN
FOR schema IN EXECUTE 'SELECT distinct schema FROM tenants' LOOP
sql := sql || format('SELECT * FROM %I.%I %s UNION ALL ', schema.schema, tbl);
END LOOP;
RETURN QUERY EXECUTE left(sql, -11);
END
$$ LANGUAGE plpgsql;
This works great, but has to be called with a row type definition at the end:
select * from app_union('my_table') t(id uuid, name text, ...);
So, how can I call my function without providing a row type?
I know that I can introspect my tables using information_schema.columns, but I'm not sure how to dynamically generate the type declaration without a lot of case statements (columns doesn't report the definition sql the way that e.g., pg_indexes does).
Even if I could dynamically generate the row declaration, it seems I would have to append it to my former function call as dynamic sql anyway, which sort of chicken/eggs the problem of returning a result set of an arbitrary type from a function.
Instead of providing the table as a string, you could provide it as type anyelement to specify the actual type of the returning data, then infer the table's name using pg_typeof. You can also use string_agg rather than a loop to build your sql:
CREATE OR REPLACE FUNCTION app_union(tbl anyelement)
RETURNS setof anyelement AS $$
BEGIN
return query execute string_agg(
distinct format('select * from %I.%I', schema, pg_typeof(tbl)::text),
' union all '
) from tenants;
END
$$ LANGUAGE plpgsql;
select * from app_union(null::my_table);
Simplified example

Postgresql, function returns query by calling another function

Postgresql 12. Want a function to return query by calling another function but don't know how to call.
create or replace function getFromA()
returns table(_id bigint, _name varchar) as $$
begin
RETURN QUERY SELECT id, name from groups;
end; $$ language plpgsql;
create or replace function getFromB()
returns table(_id bigint, _name varchar) as $$
begin
return query select getFromA();
end; $$ language plpgsql;
select getFromB();
gets error:
SQL Error [42804]: ERROR: structure of query does not match function result type
Detail: Returned type record does not match expected type bigint in column 1.
Where: PL/pgSQL function getfromb() line 3 at RETURN QUERY
How to fix this?
The problem is in getFromB():
return query select getFromA();
Unlike some other databases, Postgres allows set-returning functions directly in the select clause. This works, but can be tricky: this returns a set, hence not the expected structure.
You would need to select ... from getFromA() instead: this way it returns the proper data structure.
create or replace function getFromB()
returns table(_id bigint, _name varchar) as $$
begin
return query select * from getFromA();
end; $$ language plpgsql;
Demo on DB Fiddle

Generic set returning function

I have a plpgsql function which act as a wrapper around several other functions, all of which are set returning functions of different data types. Through this function, I am trying to get a generic set returning function which can return set of any data type. The data type of record is decided based on the input table name and column name.
Here is the code snippet:
create function cs_get(tablename text, colname text) returns setof record as $$
declare
type_name text;
ctype text;
loadfunc text;
result record;
begin
select data_type into type_name from information_schema.columns where table_name = tablename and column_name = colname;
if type_name is null then
raise exception 'Table % doesnot exist or does not have attribute % ',tablename, colname;
end if;
ctype := cs_get_ctype(type_name);
loadfunc := 'select * from cs_get_'||ctype||'('''||tablename||''','''||colname||''')';
for result in execute loadfunc loop
return next result;
end loop;
return;
end; $$ language plpgsql;
Suppose the column is of type integer (corresponding c type is int64), loadfunc would be
select * from cs_get_int64(tablename, colname)
cs_get_int64 is a set-returning function defined in a C library which returns values of type int64.
However, this gives an error saying
ERROR: a column definition list is required for functions returning "record" at character 15
The easiest way to achieve this is to return a setof text for every data type. But that of course is not a clean way to do this. I have tried replacing loadFunc with
select cs_get_int64::integer from cs_get_int64(tablename, colname)
which is required to use records. But, this did not help.
My question now is:
Is it possible to create such a generic set returning function? If yes, how?
The answer is yes. But it's not trivial.
As long as you return anonymous records (returns setof record) a column definition list is required for the returned record to work with it in SQL. Pretty much what the error message says.
There is a (limited) way around this with polymorphic types:
CREATE OR REPLACE FUNCTION cs_get(_tbl_type anyelement, _colname text)
RETURNS SETOF anyelement AS
...
Detailed explanation int this related answer (climax in the last chapter!):
Refactor a PL/pgSQL function to return the output of various SELECT queries

Postgres Dynamic Query Function

I need to create a function that will run a query and return the results with the table name and the column name being arugments given to the function. I currently have this:
CREATE OR REPLACE FUNCTION qa_scf(tname character varying, cname character varying)
RETURNS SETOF INT AS
$BODY$
BEGIN
RETURN QUERY SELECT * FROM tname WHERE cname !='AK' AND cname!='CK';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
This gives me the error "Relation 'tname' des not exist" when run. I'm new to function creating for Postgres, so any help is appreciated. I feel like the return int is wrong, but I don't know what else to put to make it return all columns for the rows returned. Thanks!
You cannot use a variable in place of an identifier like that. You need to do it with dynamic queries. It will look something like this:
EXECUTE 'SELECT * FROM ' || quote_ident(tname)
|| ' WHERE ' || quote_ident(cname) || ' NOT IN (''AK'',''CK'');'
INTO result_var;
If you are using PostgreSQL 9.1 or above, you can use the format() function which makes constructing this string much easier.
Table and column names can not be specified as parameters or variables without dynamically constructing a string to execute as a dynamic statement. Postgres has excellent introductory documentation about executing dynamic statements. It's important to properly quote identifiers and literals with quote_ident() or quote_literal(). The format() function helps clean up dynamic sql statement construction. Since you declare the function to return SETOF INTEGER, you should select the integer field you want, not *.
CREATE OR REPLACE FUNCTION qa_scf(tname text, cname text)
RETURNS SETOF INTEGER AS
$BODY$
BEGIN
RETURN QUERY EXECUTE format(
'SELECT the_integer_field FROM %I WHERE %I NOT IN (%L, %L)',
tname, cname, 'AK', 'CK'
);
END;
$BODY$
LANGUAGE plpgsql;

Return a query from a function?

I am using PostgreSQL 8.4 and I want to create a function that returns a query with many rows.
The following function does not work:
create function get_names(varchar) returns setof record AS $$
declare
tname alias for $1;
res setof record;
begin
select * into res from mytable where name = tname;
return res;
end;
$$ LANGUAGE plpgsql;
The type record only allows single row.
How to return an entire query? I want to use functions as query templates.
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
BEGIN
RETURN QUERY
SELECT t.col_a, t.col_b -- must match RETURNS TABLE
FROM mytable t
WHERE t.name = _tname;
END
$func$ LANGUAGE plpgsql;
Call like this:
SELECT * FROM get_names('name')
Major points:
Use RETURNS TABLE, so you don't have to provide a list of column names with every call.
Use RETURN QUERY, much simpler.
Table-qualify column names to avoid naming conflicts with identically named OUT parameters (including columns declared with RETURNS TABLE).
Use a named variable instead of ALIAS. Simpler, doing the same, and it's the preferred way.
A simple function like this could also be written in LANGUAGE sql:
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
SELECT t.col_a, t.col_b --, more columns - must match RETURNS above
FROM mytable t
WHERE t.name = $1;
$func$ LANGUAGE sql;