How to insert rows to table in a loop - sql

I have the following plpgsql function in PostgreSQL:
CREATE OR REPLACE FUNCTION func1()
RETURNS SETOF type_a AS
$BODY$
declare
param text;
sqls varchar;
row type_a;
begin
code.....
sqls='select * from func3(' || param || ') ';
for row in execute sqls LOOP
return next row;
END LOOP;
end if;
return;
end
$BODY$
LANGUAGE plpgsql VOLATILE
I want to add an insert statment into the loop, so that the loop will work as it is now but also all rows will be saved in a table.
for row in execute sqls LOOP
INSERT INTO TABLE new_tab(id, name)
return next row;
the thing is that I don't know how to do that... the insert statment normaly has syntax of:
INSERT INTO new_tab(id, name)
SELECT x.id, x.name
FROM y
but this syntax doesn't fit here. There is no query to select rows from.... the rows are in the loop.

Basic insert with values looks like this:
INSERT INTO table_name (column1,column2,column3,...)
VALUES (value1,value2,value3,...);
Based on the additional comments you need to use cursor instead of execute sqls.

No need for a loop, you can use insert .. select ... returning in dynamic SQL just as well:
create or replace function func1()
returns table (id integer, name text)
as
$$
declare
param text;
begin
param := ... ;
return query execute
'insert into new_tab (id, name)
select id, name
from func3($1)
returning *'
using param;
end;
$$
language plpgsql;
Note that I used a parameter placeholder and the USING clause instead of concatenating the parameter into the query - much more robust.

Related

How do I return a table from a function with a bespoke column name?

This function works:
CREATE OR REPLACE FUNCTION public.a()
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a from ztable';
END;
$function$;
But when I try to add some text to the column name:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as $1_a from ztable' using prefix;
END;
$function$;
This just fails as a syntax error on $1.
Or:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as '||prefix||'_a from ztable';
END;
$function$;
select * from a('some prefix') doesn't work.
Is there some other syntax that does the job?
That's simply not possible. SQL does not allow dynamic column names.
You must assign a column alias with the call. Like:
SELECT a AS prefix_a FROM public.a();
Or in a column definition list directly attached to the function:
SELECT * FROM public.a() AS f(prefix_a);
Or, while dealing with a single output column, even just:
SELECT * FROM public.a() AS prefix_a;
See:
RETURNING rows using unnest()?

How to create a procedure that returns a set of rows from a table in postgreSQL

How do i create a Procedure that returns a set of rows from a table?
or is it even possible to return a tabular result set with procedure.
I tried adding returns setof students like you do in a function and table(id int) but it doesn't work.
SAMPLE CODE:
CREATE OR REPLACE PROCEDURE getStudents()
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM STUDENTS
COMMIT;
RETURN;
END;
$$;
I can call it but it says query has no destination for result data
Procedures aren't meant to return data, that's what functions are for.
You can use a plain SQL function for this, no need for PL/pgSQL:
CREATE OR REPLACE funct get_students()
returns setof student
LANGUAGE sqö
AS $$
select *
from students;
$$;
Then use it like a table:
select *
from get_students();
There is also no need for a commit.
Try to use function instead of procedure. I usually use this.
You need to create a ctype for fetching the data.
Put whatever columns you have to fetch from STUDENTS table.
Syntax is as follows:
CREATE TYPE students_data_ctype AS
(
column_1 int4,
column_2 varchar(100),
column_3 varchar(500)
)
Then create a funcction :
CREATE
OR
REPLACE
FUNCTION PUBLIC.getStudents
()
RETURNS SETOF students_data_ctype AS $BODY$ DECLARE res
students_data_ctype;
BEGIN
FOR res IN
SELECT
column_1,
column_2,
column_3
FROM
STUDENTS
LOOP RETURN NEXT res;
END LOOP;
END
; $BODY$ LANGUAGE 'plpgsql'
GO
Function call :
Select * FROM getStudents()
Taddaaa! You will get your data.

Explain - inserts only one row

I'm trying to manually save optimizer plan for further analysis, like this:
do $$
declare
tmp text;
begin
explain
select * from public.some_table where 1=2 into tmp;
insert into public.plans(plan) values (tmp);
end; $$
But when I select it later, I see it only saved first row from the explain statement:
Result (cost=0.00..82.97 rows=1 width=114)
How can I make it to save the whole plan?
Because explain cannot be used like e.g. a SELECT this is a bit tricky and you need dynamic SQL for this.
The following worked for me:
do
$$
declare
plan_line record;
begin
for plan_line in execute 'explain select * from public.some_table where 1=2' loop
insert into plans values (plan_line."QUERY PLAN");
end loop;
end;
$$
Having the statement to be explained in a string makes things a bit more complicated.
If I needed that on a regular basis, I would probably create a function that does this:
create or replace function explain(to_explain text)
returns setof text
as
$$
declare
plan_line record;
begin
for plan_line in execute 'explain '||to_explain loop
return next plan_line."QUERY PLAN";
end loop;
end;
$$
language plpgsql;
Then you can do something like:
insert into plans
select *
from explain('select ...');

How to iterate over a record when the columns are dynamic

I have this function in postgres which takes PVH_COLS_DYNA that contains the columns that are going in to the query:
CREATE OR REPLACE FUNCTION DRYNAMIC_DATA_F(PVH_COLS_DYNA VARCHAR) RETURNS numeric AS $$
DECLARE
VV_QUERY_DINAMIC VARCHAR;
VV_ROW_RECORD record;
BEGIN
VV_QUERY_DINAMIC:=' SELECT '|| PVH_COLS_DYNA ||' FROM as_detalle_carga WHERE fk_id_carga_cartera = 1234 ;';
FOR VV_ROW_RECORD IN EXECUTE VV_QUERY_DINAMIC LOOP
raise notice ' data % ', VV_ROW_RECORD.???????;
END LOOP;
return 1;
END;
$$ LANGUAGE plpgsql;
How can I get the data from the record variable VV_ROW_RECORD, since the columns are dynamic?
VV_ROW_RECORD.1
VV_ROW_RECORD.?1
VV_ROW_RECORD.[1]
VV_ROW_RECORD.?????
You cannot reference columns like array items, columns have to be referenced by name.
The dynamic part is not getting the row in your example, but referencing each column.
CREATE OR REPLACE FUNCTION dynamic_data_f(pvh_cols_dyna text)
RETURNS numeric AS
$func$
DECLARE
_row as_detalle_carga%ROWTYPE;
_col text;
_data text;
BEGIN
SELECT *
INTO _row
FROM as_detalle_carga
WHERE fk_id_carga_cartera = 1234;
FOREACH _col IN ARRAY string_to_array(pvh_cols_dyna, ',')
LOOP
EXECUTE format('SELECT ($1).%I::text', trim(_col))
USING _row
INTO _data;
RAISE NOTICE 'data: % ', _data;
END LOOP;
RETURN 1;
END
$func$ LANGUAGE plpgsql;
%I is an argument to format(), properly escaping identifiers as needed.
$1 in the query string for EXECUTE is a parameter filled in by the USING clause (not to be confused with function parameters!).
Related answers (with more explanation):
Postgres pl/pgsql ERROR: column "column_name" does not exist
Iterating over integer[] in PL/pgSQL
How to use EXECUTE FORMAT ... USING in postgres function
You cannot iterate record columns directly. You have to convert it first into something iterable, like json or hstore.
FOR vv_row_record IN EXECUTE vv_query_dynamic LOOP
FOR vv_row_record_pairs IN SELECT * FROM json_each(row_to_json(vv_row_record)) LOOP
RAISE NOTICE ' field "%" in json is % ',
vv_row_record_pairs.key,
vv_row_record_pairs.value;
END LOOP;
-- OR
FOR vv_row_record_pairs IN SELECT * FROM each(hstore(vv_row_record)) LOOP
RAISE NOTICE ' field "%" in text representation is % ',
vv_row_record_pairs.key,
vv_row_record_pairs.value;
END LOOP;
END LOOP;

How do I pass in the array output of SQL query into a PostgreSQL (PL/pgSQL) function?

I am able to do the following in SQL where an "array" of user_ids are passed into the where clause of a SQL query.
select * from users where id in (select user_id from profiles);
I would like to do the same thing but pass the "array" into a PostgreSQL (PL/pgSQL) function as shown below. How do I declare the function and work with the "array" within the function?
select * from users_function(select user_id from profiles);
CREATE OR REPLACE FUNCTION users_function(....)
RETURNS void AS
$BODY$
....
Declare an array datatype [] in the function then use the aggregate function array_agg to transform the select statement into an array.
CREATE OR REPLACE FUNCTION users_function(myints integer[])
$$
BEGIN
-- you need to find the bounds with array_lower and array_upper
FOR i in array_lower(myints, 1) .. array_upper(myints, 1) LOOP
Raise Notice '%', myints[i]::integer;
END LOOP;
END;
$$
select * from users_function(array_agg((select user_id from profiles)));
I could not get the nate c's array_agg approach as I described above. This is an option:
select * from test_array('{1,2}');
CREATE OR REPLACE FUNCTION test_array(user_ids integer[])
RETURNS void AS
$$
declare
begin
FOR i in array_lower(user_ids, 1) .. array_upper(user_ids, 1) LOOP
RAISE NOTICE '%', user_ids[i]::integer;
END LOOP;
end
$$
LANGUAGE plpgsql;