Pass a schema name and table name dynamically in FROM in a select query in Postgres - postgresql

Pass a schema name and table name dynamically in FROM in a select query in postgres.
I need to call a table dynamically in a from (in the select clause)
CREATE OR REPLACE FUNCTION xx.fn_build_test_(
IN p_var_archive_schema character varying,
IN p_var_archive_table character varying)
RETURNS record AS
$BODY$
declare
l_var_archive_schema VARCHAR;
l_var_archive_table VARCHAR;
l_var_test VARCHAR[];
BEGIN
l_var_archive_schema := p_var_archive_schema;
l_var_archive_table := p_var_archive_table;
SELECT array
( SELECT TO_CHAR(column_name,'YYYYMMDD')
FROM "test_table"
WHERE col1 = 1)
INTO l_var_test;
END;
$BODY$
LANGUAGE plpgsql
VOLATILE SECURITY INVOKER;
I need values for:
l_var_archive_schema VARCHAR;
l_var_archive_table VARCHAR;
in place of the test table

You do not need those local variables for schema and table.
Use format option to construct the queries and EXECUTE to run it dynamically
CREATE OR REPLACE FUNCTION xx.fn_build_test_(
IN p_var_archive_schema character varying,
IN p_var_archive_table character varying )
RETURNS record AS
$BODY$
DECLARE
l_var_test VARCHAR[];
BEGIN
SELECT array
( SELECT TO_CHAR(column_name,'YYYYMMDD')
FROM "test_table"
WHERE col1 = 1
) INTO l_var_test;
EXECUTE format (
'select col_name FROM %I.%I',
p_var_archive_schema,p_var_archive_table)
--INTO rec_variable;
END;
$BODY$
LANGUAGE plpgsql
VOLATILE SECURITY INVOKER;
If you want to return the result of a dynamic query you may use
RETURNS TABLE option and then do RETURN QUERY EXECUTE to return results from the query.

You can use dynamic SQL. For example change the SQL in the function as following. The function needs further correction as by definition it is supposed to return a record but currently not returning any value.
EXECUTE 'SELECT array
( SELECT TO_CHAR(column_name,''YYYYMMDD'')
FROM '||l_var_archive_schema||'.'||l_var_archive_table||' WHERE col1 = 1)'
INTO l_var_test;

Related

How to get the fields' information from a SQL statement in PostgreSQL?

I want to get fields' description from a SQL statement. That is, I want create a function, use a sql statement as the input parameter, returns a jsonb value to decribe the fields' information.
If I know the table name, I can do this:
SELECT column_name, data_type
FROM information_schema."columns"
WHERE "table_name"='TABLE-NAME'
But if I only have the select sql statement, such as select 0::bigint as fid, 'A'::text as fname;,how can I get the column_name and data_type In a function.
Below is the structure for then function when the I know the table name:
create or replace function public.getfieldsfromtable(vtable text)
returns jsonb
language 'plpgsql'
cost 100
volatile
as $body$ declare
jfields jsonb='[]'::jsonb;
begin
select
array_to_json(array_agg(row_to_json(t)))
into jfields
from (
select column_name, data_type
from information_schema."columns"
where "table_name"=vtable
) t;
return jfields;
end;
$body$;
But What Can I Do If I Just Have A Select SQL Statement?
create or replace function public.getfieldsfromsql(vsql text)
returns jsonb
language 'plpgsql'
cost 100
volatile
as $body$ declare
jfields jsonb='[]'::jsonb;
begin
... what can i do?
return jfields;
end;
$body$;
It can be easy, if you do it in C extension, but it is almost impossible in PL/pgSQL. For stored procedures, there is not an API, how to detect structure of result without query execution.

Avoid 'ambiguous column' in simple PostgreSQL function returning table

Suppose I have the following table, function and execution:
create table mytable (a INTEGER, b INTEGER);
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
begin
return query select a,b from mytable;
end;
$body$
language plpgsql STABLE;
select * from test(1);
I get an 'ambiguous column name' error. I can get rid of it by changing the selection to "select t.a, t.b from mytable t" (per some similar-ish posts). But it seems very odd to have to qualify the column names when there is only 1 table in my query. I'm porting code that has quite a lot of stored procedures selecting from single tables (in various ways) and returning a table with columns that have the same name. Is there a better way than this of avoiding the error, and still having an output table with the same column names?
Thanks for any leads.
you can (you should) to use aliases.
create table mytable (a INTEGER, b INTEGER);
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
begin
return query select mt.a, mt.b from mytable mt;
end;
$body$
language plpgsql STABLE;
select * from test(1);
maybe thats an option, using format and query execute:
( as the error says, it doesnt know which a to take, the pl/pgSQL variable or a columname )
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
declare
lsQueryExecute text;
begin
lsQueryExecute = format('select a,b from mytable');
return query execute lsQueryExecute;
end;
$body$
language plpgsql STABLE;

Postgresql query across different tables with dynamic query

I'm trying to get a customer id which can be placed in one of ten different tables. I don't want to hard code those table names to find it so I tried postgresql function as follows.
create or replace FUNCTION test() RETURNS SETOF RECORD AS $$
DECLARE
rec record;
BEGIN
select id from schema.table_0201_0228 limit 1 into rec;
return next rec;
select id from schema.table_0301_0331 limit 1 into rec;
return next rec;
END $$ language plpgsql;
select * from test() as (id int)
As I'm not familiar with postgresql function usage, how can I improve the code to replace 'schema.table1' with a variable, loop each table and return the result?
NOTE: table names may change overtime. For example, table_0201_0228 and table_0301_0331 are for February and March respectively.
You need dynamic SQL for that:
create or replace FUNCTION test(p_schema text)
RETURNS table(id int)
AS $$
DECLARE
l_tab record;
l_sql text;
BEGIN
for l_tab in (select schemaname, tablename
from pg_tables
where schemaname = p_schema)
loop
l_sql := format('select id from %I.%I limit 1', l_tab.schemaname, l_tab.tablename);
return query execute l_sql;
end loop;
END $$
language plpgsql;
I made the schema name a parameter, but of course you can hard-code it. As the function is defined as returns table there is no need to specify the column name when using it:
select *
from test('some_schema');

plpgsql - selecting array of multiple columns

I am trying to create procedure which selects data, processes and returns them, but I am struggling how to define array variable for multiple columns.
This works:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT) AS
$body$
DECLARE
l_rows BIGINT[];
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT 1 AS id
UNION
SELECT 2 AS id
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE 'plpgsql'
But I want to do this for 2 or more columns without using composite type or rowtype:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT, text VARCHAR2) AS
$body$
DECLARE
l_rows -- what should I put here?
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT 1 AS id, 'test' AS text
UNION
SELECT 2 AS id, 'test2' AS text
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE 'plpgsql'
In oracle I could define record type and then table type of it, but I can't find how to do this in postgres. Maybe using wrong keywords when searching...
edit: this is how I would do this in Oracle (without returning).
DECLARE
TYPE t_row IS RECORD(
id NUMBER
,text VARCHAR2(10));
TYPE t_tbl IS TABLE OF t_row;
l_rows t_tbl := t_tbl(); --how to do this in postgres?
BEGIN
SELECT *
BULK COLLECT
INTO l_rows
FROM (SELECT 1 AS id
,'test' AS text
FROM dual
UNION ALL
SELECT 2 AS id
,'test' AS text
FROM dual);
END;
Anything similiar in postgres? Like record but for array.
You would do the same in Postgres:
Create the record type:
create type footype as (id bigint, text_ varchar);
Then use an array of that type in your function:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT, text VARCHAR) AS
$body$
DECLARE
l_rows footype[];
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT (1, 'test')
UNION
SELECT (2, 'test2')
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE plpgsql;
Online example: http://rextester.com/UMRZFO54266
The expression (1, 'test') creates a single value of record type. As that is assigned to a typed variable, there is no need to alias the columns (and in fact you can't do that anyway).
Unrelated, but: the language name is an identifier. Do not put that in single quotes.
Note that text is also a keyword in Postgres because it's a data type. You shouldn't use it as a column name

Merge multiple result tables and perform final query on result

I have a function returning table, which accumulates output of multiple calls to another function returning table. I would like to perform final query on built table before returning result. Currently I implemented this as two functions, one accumulating and one performing final query, which is ugly:
CREATE OR REPLACE FUNCTION func_accu(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
GATEWAY_ID integer;
BEGIN
FOR GATEWAY_ID IN
execute format(
'SELECT id FROM %1$I.gateway WHERE location_id=%2$L'
, SCHEMA_CUSTOMER, LOCATION_ID)
LOOP
RETURN QUERY execute format(
'SELECT * FROM get_available_networks_gw(%1$L, %2$L)'
, GATEWAY_ID, SCHEMA_CUSTOMER);
END LOOP;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION func_query(LOCATION_ID INTEGER, SCHEMA_CUSTOMER TEXT)
RETURNS TABLE("networkid" integer, "count" bigint) AS $$
DECLARE
BEGIN
RETURN QUERY execute format('
SELECT networkid, max(count) FROM func_accu(%2$L, %1$L) GROUP BY networkid;'
, SCHEMA_CUSTOMER, LOCATION_ID);
END;
$$ LANGUAGE plpgsql;
How can this be done in single function, elegantly?
Both functions simplified and merged, also supplying value parameters in the USING clause:
CREATE OR REPLACE FUNCTION pg_temp.func_accu(_location_id integer, schema_customer text)
RETURNS TABLE(networkid integer, count bigint) AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT f.networkid, max(f.ct)
FROM %I.gateway g
, get_available_networks_gw(g.id, $1) f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1'
, _schema_customer)
USING _schema_customer, _location_id;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM func_accu(123, 'my_schema');
Related:
Dynamically access column value in record
I am using alias names for the columns returned by the function (f(networkid, ct)) to be sure because you did not disclose the return type of get_available_networks_gw(). You can use the column names of the return type directly.
The comma (,) in the FROM clause is short syntax for CROSS JOIN LATERAL .... Requires Postgres 9.3 or later.
What is the difference between LATERAL and a subquery in PostgreSQL?
Or you could run this query instead of the function:
SELECT f.networkid, max(f.ct)
FROM myschema.gateway g, get_available_networks_gw(g.id, 'my_schema') f(networkid, ct)
WHERE g.location_id = $2
GROUP BY 1;