How to Return a Table That Was Created Within Function? - plpgsql

I have a PL/pgSQL function that creates a table. I would like to return the table that was created as part of a "Return Table" function. Here is an example of the type of function I'm talking about:
CREATE OR REPLACE FUNCTION my_schema.new_foo_table(character varying)
RETURNS table(row_id bigint,
catalog_id varchar,
value numeric(5,4)) AS
$BODY$
DECLARE
v_table_name ALIAS FOR $1;
cmd text;
BEGIN
cmd := 'CREATE TABLE '||v_table_name||'
(row_id bigint,
catalog_id varchar,
value numeric(5,4))';
EXECUTE cmd;
cmd := 'INSERT INTO '||v_table_name||'
SELECT row_id, catalog_id, sum(value)
FROM other_table
GROUP BY row_id, catalog_id';
EXECUTE cmd;
RETURN --results of select * from v_table_name;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
How would I return the results of the new table I created? I'm guess the return would have to use some type of dynamic SQL "execute" statement since the name of the newly created table is in a variable.

Yes this can be done using RETURN QUERY EXECUTE 'command string'. For your example it would look like this:
CREATE OR REPLACE FUNCTION my_schema.new_foo_table(character varying)
RETURNS table(row_id bigint,
catalog_id varchar,
value numeric(5,4)) AS
$BODY$
DECLARE
arg_table_name ALIAS FOR $1;
v_table_name varchar;
cmd text;
BEGIN
v_table_name := quote_ident(arg_table_name);
cmd := 'CREATE TABLE '||v_table_name||'
(row_id bigint,
catalog_id varchar,
value numeric(5,4))';
EXECUTE cmd;
cmd := 'INSERT INTO '||v_table_name||'
SELECT row_id, catalog_id, sum(value)
FROM other_table
GROUP BY row_id, catalog_id';
EXECUTE cmd;
RETURN QUERY EXECUTE 'select * from v_table_name';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
For additional information see section 40.6.1.2. in the POSTGRESQL documentation: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING

Related

How to nest variable in query string passed to function

I need to be able to get the value stored inside rec_key.empname when I call this function:
CREATE OR REPLACE FUNCTION public.txt(text)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
declare
var_param text;
var_req TEXT;
rec_key record;
cur_key CURSOR FOR Select empname::varchar from employee;
BEGIN
open cur_key;
loop
fetch cur_key into rec_key;
EXIT WHEN NOT FOUND;
var_req :=
'
' || $1 || '
';
return query execute var_req;
end loop;
close cur_key;
END
$function$
;
What do I have to change to get the desired empname when calling the function?
If I call it like this it doesn't work: :(
select * from public.txt('select empid, age::integer,''''''|rec_key.empname|''''''::varchar from employee') as (empid integer, age integer, empname varchar)
To address the question asked:
CREATE OR REPLACE FUNCTION public.txt(_sql text)
RETURNS SETOF record
LANGUAGE plpgsql AS
$func$
DECLARE
_rec record;
BEGIN
FOR _rec IN
SELECT empname::text FROM employee
LOOP
RETURN QUERY EXECUTE _sql
USING _rec.empname;
END LOOP;
END
$func$;
Call:
SELECT * FROM public.txt('SELECT empid, age::integer, $1 AS empname FROM employee')
AS (empid integer, age integer, empname varchar);
The example does not make any sense, though, and all of it could be replaced with a simple query. See my anser to your earlier question:
Doesn't find variable when passing query as parameter
Use the much simpler implicit cursor of a FOR loop. See:
Cursor based records in PostgreSQL
Truncating all tables in a Postgres database
Pass the variable as value with a USING clause. $1 is the symbol to reference the first USING argument. See:
Replace double quotes with single quotes in Postgres (plpgsql)

How to do postgresql select query funciton using parameter?

I want to create a postgresql funciton that returns records. But if I pass an id parameter, it should be add in where clause. if I do not pass or null id parameter, where clasuse will not add the query.
CREATE OR REPLACE FUNCTION my_func(id integer)
RETURNS TABLE (type varchar, total bigint) AS $$
DECLARE where_clause VARCHAR(200);
BEGIN
IF id IS NOT NULL THEN
where_clause = ' group_id= ' || id;
END IF ;
RETURN QUERY SELECT
type,
count(*) AS total
FROM
table1
WHERE
where_clause ???
GROUP BY
type
ORDER BY
type;
END
$$
LANGUAGE plpgsql;
You can either use one condition that takes care of both situations (then you don't need PL/pgSQL to begin with):
CREATE OR REPLACE FUNCTION my_func(p_id integer)
RETURNS TABLE (type varchar, total bigint)
AS $$
SELECT type,
count(*) AS total
FROM table1
WHERE p_id is null or group_id = p_id
GROUP BY type
ORDER BY type;
$$
LANGUAGE sql;
But an OR condition like that is typically not really good for performance. The second option you have, is to simply run two different statements:
CREATE OR REPLACE FUNCTION my_func(p_id integer)
RETURNS TABLE (type varchar, total bigint)
AS $$
begin
if (p_id is null) then
return query
SELECT type,
count(*) AS total
FROM table1
GROUP BY type
ORDER BY type;
else
return query
SELECT type,
count(*) AS total
FROM table1
WHERE group_id = p_id
GROUP BY type
ORDER BY type;
end if;
END
$$
LANGUAGE plgpsql;
And finally you can build a dynamic SQL string depending the parameter:
CREATE OR REPLACE FUNCTION my_func(p_id integer)
RETURNS TABLE (type varchar, total bigint)
AS $$
declare
l_sql text;
begin
l_sql := 'SELECT type, count(*) AS total FROM table1 '
if (p_id is not null) then
l_sql := l_sql || ' WHERE group_id = '||p_id;
end if;
l_sql := l_sql || ' GROUP BY type ORDER BY type';
return query execute l_sql;
end;
$$
LANGUAGE plpgsql;
Nothing is required just to use the variable as it is for more info please refer :plpgsql function parameters

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

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;

create child table function using cursor in postgresql

CREATE FUNCTION create_child1()
RETURNS TABLE(sys_user_id integer,
sys_service_id integer
)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
DECLARE
curr_id CURSOR IS
SELECT id FROM users WHERE id in (3089,3090,3091,3092);
v_id bigint;
BEGIN
OPEN curr_id;
LOOP
FETCH curr_id INTO v_id;
EXIT WHEN not found ;
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I (
sys_user_id integer,
sys_service_id integer
id bigint NOT NULL primary key
)
INHERITS (telemetry_master)
WITH (
OIDS=FALSE
)', 'telemetry_' || v_id);
end loop;
close curr_id;
fetch next from curr_id into v_id;
END
$BODY$ LANGUAGE plpgsql;
You do not need an explicit cursor in your function. You can use a simple FOR ... IN ... LOOP.
It is unclear what you want to return from the function. For example, it can return a readable text about each created table.
CREATE OR REPLACE FUNCTION create_child1()
RETURNS SETOF text LANGUAGE plpgsql
AS $BODY$
DECLARE
v_id int;
BEGIN
FOR v_id IN 3089..3092 LOOP
EXECUTE format('
CREATE TABLE IF NOT EXISTS telemetry_%s (
sys_user_id integer,
sys_service_id integer,
id bigint NOT NULL primary key
)
INHERITS (telemetry_master)', v_id);
RETURN NEXT format('telemetry_%s created.', v_id);
END LOOP;
END $BODY$;
Use:
SELECT create_child1();
create_child1
-------------------------
telemetry_3089 created.
telemetry_3090 created.
telemetry_3091 created.
telemetry_3092 created.
(4 rows)
If the ids are not consecutive you can use unnest(), e.g.:
FOR v_id IN SELECT id FROM unnest(array[3000,3001,3020,3021]) AS id LOOP

Greenplum/Postgres 8 function dynamic result set?

I need to write a function that returns a table with unknown number of columns.
If i receive 'None' in column input parameter then that column shouldn't be included in the output. In postgres 9+ there is a solution for this problem.
something like below:
CREATE OR REPLACE FUNCTION data_of(id integer,col1 varchar,col2 varchar, col3 varchar)
RETURNS TABLE (count_rec, dimensions text[] ) AS
$func$
DECLARE
_dimensions text := 'col1, col2, col3'; -- If i receive 'None' in input param then i exclude that from column list
BEGIN
RETURN QUERY EXECUTE format('
SELECT count(*) as count_rec,
string_to_array($1) -- AS dimensions
FROM x
WHERE id = $2'
, _dimensions)
USING _dimensions , _id;
END
$func$ LANGUAGE plpgsql;
But in Greenplum (Postgres 8.2) i could not find any. Is there any similar solution?
thanks
You have 2 options to do it: use set-returning function returning "record" or returning your custom type.
First option:
create table test (a int, b int, c int, d varchar, e varchar, f varchar);
insert into test select id, id*2, id*3, (id*4)::varchar, (id*4)::varchar, (id*4)::varchar from generate_series(1,10) id;
create or replace function test_func(column_list varchar[]) returns setof record as $BODY$
declare
r record;
begin
for r in execute 'select ' || array_to_string(column_list, ',') || ' from test' loop
return next r;
end loop;
return;
end;
$BODY$
language plpgsql
volatile;
select * from test_func(array['a','c','e']) as f(a int, c int, e varchar);
Second option:
create table test (a int, b int, c int, d varchar, e varchar, f varchar);
insert into test select id, id*2, id*3, (id*4)::varchar, (id*4)::varchar, (id*4)::varchar from generate_series(1,10) id;
create type testtype as (
a int,
c int,
e varchar
);
create or replace function test_func() returns setof testtype as $BODY$
declare
r testtype;
begin
for r in execute 'select a,c,e from test' loop
return next r;
end loop;
return;
end;
$BODY$
language plpgsql
volatile;
select * from test_func();
But I'm 99% sure you're trying to do something wrong. In Greenplum the result of function execution cannot be used as a "table" in join conditions, because the function executes on the master. You even won't be able to create a table out of the last query returning the data from your function because of this limitation
In short, this is not a recommended way to work with data in Greenplum