Loop through a composite array in plpgsql - postgresql

I want to define a composite array , initialize three elements of the array and then iterate over each of the elements in a loop. I need this loop and to iterate over it as a requirement for an issue that I have.
I have tried for several days to write the code and refer to several resources on net but this has not worked. Can someone help on it. Here is the code, even the first initialization is not working so I don't have code for rest of the requirement (to iterate after the initialization)
CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);
CREATE OR REPLACE function temp_n_bulk_load2()
returns void
as
$$
declare
v_t temp_n_inv_item[];
BEGIN
v_t[1] := ARRAY ['Item1',1,10];
v_t[2] := ARRAY ['Item2',2,20];
v_t[3] := ARRAY ['Item3',3,30];
-- raise notice 'first array % ', v_t[1];
--raise notice 'first array % ', v_t[2];
--raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
null; --added as a temporary placeholder
END;
$$
language plpgsql;
--select temp_n_bulk_load2()
Regards,
DbuserN
UPDATE
Surprisingly, after posting the question, I saw one reference and assignment is now working but I try to change the return type to the array which is not working, (which is an additional issue), though assignment is fixed now as below, but the error the below gives is "ERROR: cannot return non-composite value from function returning composite type"
Revised code
:
CREATE OR REPLACE function temp_n_bulk_load2()
returns temp_n_inv_item
as
$$
declare
v_t temp_n_inv_item[];
BEGIN
v_t[1] := row ('Item1',1,10);
v_t[2] := row ('Item2',2,20);
v_t[3] := row ('Item3',3,30);
raise notice 'first array % ', v_t[1];
raise notice 'first array % ', v_t[2];
raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
null; --added as a temporary placeholder
return v_t;
END;
$$
language plpgsql;

PLpgSQL has special statement for iteration over array. For large arrays it is much more effective (is not too significant for small arrays):
create type tp as (a int, b int);
do $$
declare a tp[];
r record;
begin
/* composite array initialization */
a = array[(1,2),(3,4),(5,6)];
/* iterate over a array */
foreach r in array a
loop
raise notice '% % %', r, r.a, r.b;
end loop;
end;
$$;
NOTICE: (1,2) 1 2
NOTICE: (3,4) 3 4
NOTICE: (5,6) 5 6
DO
Don't afraid read a documentation.

Here you are creating array of data type you generated using
CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);
So simply return array of data type as temp_n_inv_item[].
CREATE OR REPLACE FUNCTION adm.temp_n_bulk_load2()
RETURNS boolean AS
$BODY$
declare
v_t temp_n_inv_item[];
BEGIN
/*CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);*/
v_t[1] := row ('Item1',1,10);
v_t[2] := row ('Item2',2,20);
v_t[3] := row ('Item3',3,30);
raise notice 'first array % ', v_t[1];
raise notice 'first array % ', v_t[2];
raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
//updated
FOR cnt in 1..(array_length(v_t,1))
LOOP
Raise notice ' array value % ', v_t[cnt];
END LOOP;
return true;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Hope this will solve your problem.

Related

pass multiple arrays as input to a function in PostgreSQL

I have a requirement to pass 2 arrays as input to a function
array 1: acct_num, salary etc
array 2:
{1011,'Unit 102, 100 Wester highway, Paramataa'}
{1012,'+61426999888'}
In above example, array 2 can be dynamic, meaning they can pass upto 500 keys
How to process each array key and the value, because I ned to store address information in addresss table and phone number in PHONE table.
I need assistance to access each element in array, but I dont know how to process second elemtn in array 2 (ex:+61426999888)
CREATE OR REPLACE FUNCTION schema.test(
arraytext character varying[],
arraydomain character varying[][])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
p_v1_1 := arraytext[1];
p_v2_1 := generate_subscripts($1, arraydomain[1]); --arraydomain[1];
p_v2_2 := arraydomain[2];
raise notice 'p_v1_1 : %', p_v1_1;
raise notice 'p_v2_1 : %', p_v2_1;
raise notice 'p_v2_2 : %', p_v2_2;
p_v2_3 := arraydomain[3];
p_v2_4 := arraydomain[4];
raise notice 'p_v2_3 : %', p_v2_3;
raise notice 'p_v2_4 : %', p_v2_4;
RETURN 0;
--EXCEPTION WHEN others THEN
-- RETURN 1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Then I use:
SELECT *
FROM schema.test(ARRAY['9361030699999'], ARRAY[['1011','Unit 102, 100 Wester highway, Paramataa'],['1012','+61426999888']]);
Here's a function showing a couple ways of accessing multidimensional arrays. One just loops through the array using a slice, which is the easiest way - the c variable simply exists so I can print the "outer" index, it's not necessary at all.
The other way accesses the values directly. However I don't know how to get each "subarray" itself via index access - e.g. ar[2:2] returns {{values}}, (ar[2:2])[1] returns NULL, and (ar[2:2])[1][1] returns the value of the item in the subarray at that index - the middle one returning NULL is something I don't get. If you could get it, then you could use ARRAY_UPPER to access all the values dynamically without using FOREACH/SLICE.
Also notice I don't declare TEXT[][] - it makes no difference.
CREATE OR REPLACE FUNCTION public.f1(ar TEXT[])
RETURNS VOID AS
$BODY$
DECLARE
_ar TEXT[];
c INTEGER := 0;
BEGIN
FOREACH _ar SLICE 1 IN ARRAY ar LOOP
c := c + 1;
FOR i IN 1..ARRAY_UPPER(_ar, 1) LOOP
RAISE NOTICE '%.%: %', c, i, _ar[i];
END LOOP;
END LOOP;
RAISE NOTICE 'Alternative: %, %', (ar[2:2])[1][1], (ar[2:2])[1][2];
END
$BODY$
LANGUAGE plpgsql IMMUTABLE;
Call:
SELECT * FROM public.f1(ARRAY[['1011','Unit 102, 100 Wester highway, Paramataa'],['1012','+61426999888']]);
Prints:
NOTICE: 1.1: 1011
NOTICE: 1.2: Unit 102, 100 Wester highway, Paramataa
NOTICE: 2.1: 1012
NOTICE: 2.2: +61426999888
NOTICE: Alternative: 1012, +61426999888

How to pass and array and modify it in a function in postgres

I am trying to write a function that does this: basically create an array with same data elements as the array that is passed to the function but with some change sometimes - like if some element is even then the flag should change from N to Y. The procedure takes as input an array that has two elements - a number and a flag. eg. (1,'N'), (2,'N'). Now if the passed number is even then the proc should modify that value and change to (2,'Y') whereas the other one remains as (1,'N').
Basically my array basics are not clear and reading through details has not helped so this question.
I tried the following but it is not working...can you please suggest:
CREATE TYPE test_n_t_num AS (
v_n double precision,
is_even character varying(1));
create function temp_n_proc_2(p_nums IN OUT test_n_t_num[])
as
$$
declare
v_nums test_n_t_num[];
v_cnt double precision;
BEGIN
v_cnt := cardinality(p_nums);
v_nums := ARRAY[]::test_n_t_num[];
for i in 1..v_cnt LOOP
if p_nums[i].v_n_double % 2 = 0 then
v_nums [i].is_even := 'Y';
p_nums [i].is_even := 'Y'
else
v_nums [i].is_even := p_nums [i].is_even;
end if;
v_nums[i] := {p_nums[i].v_n,v_nums [i].is_even};
END LOOP;
END;
$$
language plpgsql;
Also later I need to loop through and print out the values in the array v_nums - one that is defined in the function.
Thank you,
Nirav
I had a similar issue when I was trying to do this sort of thing back in the day. Basically you can't assign to composite objects using array notation, i.e. your_array[1].field := value doesn't work. Here's something that does (I don't use cardinality since I'm still on 9.3 and that was added in 9.4):
CREATE TYPE public.test1 AS (a INTEGER, is_even BOOLEAN);
CREATE OR REPLACE FUNCTION f1(ar INOUT public.test1[]) AS $$
DECLARE
t public.test1;
BEGIN
RAISE NOTICE '%', ar;
FOR i IN 1..ARRAY_LENGTH(ar, 1) LOOP
t := ar[i];
t.is_even := t.a % 2 = 0;
ar[i] := t;
END LOOP;
RAISE NOTICE '%', ar;
END
$$ LANGUAGE plpgsql;
So basically create a variable of that type, read the indexed item into the variable, modify the fields, then copy the variable's content back to the array.
SELECT * FROM f1('{"(1,)","(4,)","(6,)"}'::public.test1[]) returns {"(1,f)","(4,t)","(6,t)"}
The messages printed (this is using pgadmin 3) are:
NOTICE: {"(1,)","(4,)","(6,)"}
NOTICE: {"(1,f)","(4,t)","(6,t)"}

How to make a loop structure out of an array in plpgsql? [duplicate]

In plpgsql, I want to get the array contents one by one from a two dimension array.
DECLARE
m varchar[];
arr varchar[][] := array[['key1','val1'],['key2','val2']];
BEGIN
for m in select arr
LOOP
raise NOTICE '%',m;
END LOOP;
END;
But the above code returns:
{{key1,val1},{key2,val2}}
in one line. I want to be able to loop over and call another function which takes parameters like:
another_func(key1,val1)
Since PostgreSQL 9.1
There is the convenient FOREACH which can loop over slices of arrays. The manual:
The target variable must be an array, and it receives successive
slices of the array value, where each slice is of the number of
dimensions specified by SLICE.
DO
$do$
DECLARE
m text[];
arr text[] := '{{key1,val1},{key2,val2}}'; -- array literal
BEGIN
FOREACH m SLICE 1 IN ARRAY arr
LOOP
RAISE NOTICE 'another_func(%,%)', m[1], m[2];
END LOOP;
END
$do$;
db<>fiddle here - with a function printing results, instead of DO
LANGUAGE plpgsql is the default for a DO statement so we can omit the declaration.
There is no difference between text[] and text[][] for the Postgres type system. See:
Initial array in function to aggregate multi-dimensional array
Postgres 9.0 or older
DO
$do$
DECLARE
arr text[] := array[['key1','val1'],['key2','val2']]; -- array constructor
BEGIN
FOR i IN array_lower(arr, 1) .. array_upper(arr, 1)
LOOP
RAISE NOTICE 'another_func(%,%)', arr[i][1], arr[i][2];
END LOOP;
END
$do$;

Postgres - Dynamically referencing columns from record variable

I'm having trouble referencing record variable type columns dynamically. I found loads of tricks online, but with regards to triggers mostly and I really hope the answer isn't "it can't be done"... I've got a very specific and simple need, see my example code below;
First I have an array containing a list of column names called "lCols". I loop through a record variable to traverse my data, replacing values in a paragraph which exactly match my column names.
DECLARE lTotalRec RECORD;
DECLARE lSQL text;
DECLARE lCols varchar[];
p_paragraph:= 'I am [names] and my surname is [surname]';
lSQL :=
'select
p.names,
p.surname
from
person p
';
FOR lTotalRec IN
execute lSQL
LOOP
-- Loop through the already created array of columns to replace the values in the paragraph
FOREACH lVal IN ARRAY lCols
LOOP
p_paragraph := replace(p_paragraph,'[' || lVal || ']',lTotalRec.lVal); -- This is where my problem is, because lVal is not a column of lTotalRec directly like this
END LOOP;
RETURN NEXT;
END LOOP;
My return value is the paragraph amended for each record in "lTotalRec"
You could convert your record to a json value using the row_to_json() function. Once in this format, you can extract columns by name, using the -> and ->> operators.
In Postgres 9.4 and up, you can also make use of the more efficient jsonb type.
DECLARE lJsonRec jsonb;
...
FOR lTotalRec IN
execute lSQL
LOOP
lJsonRec := row_to_json(lTotalRec)::jsonb;
FOREACH lVal IN ARRAY lCols
LOOP
p_paragraph := replace(p_paragraph, '[' || lVal || ']', lJsonRec->>lVal);
END LOOP;
RETURN NEXT;
END LOOP;
See the documentation for more details.
You can convert a row to JSON using row_to_json(), and then retrieve the column names using json_object_keys().
Here's an example:
drop table if exists TestTable;
create table TestTable (col1 text, col2 text);
insert into TestTable values ('a1', 'b1'), ('a2', 'b2');
do $$declare
sql text;
rec jsonb;
col text;
val text;
begin
sql := 'select row_to_json(row) from (select * from TestTable) row';
for rec in execute sql loop
for col in select * from jsonb_object_keys(rec) loop
val := rec->>col;
raise notice 'col=% val=%', col, val;
end loop;
end loop;
end$$;
This prints:
NOTICE: col=col1 val=a1
NOTICE: col=col2 val=b1
NOTICE: col=col1 val=a2
NOTICE: col=col2 val=b2
DO

I am trying to unnet an array in other to query the postgres DB

I am call the function but it is returning error that array value must start with "{" or dimension information using
Create or Replace Function get_post_process_info(IN v_esdt_pp character varying[])
Returns setof Record as
$$
Declare
post_processes RECORD;
esdt_value character varying;
v_sdsname character varying[];
v_dimension character varying[];
counter int := 1;
Begin
-- to loop through the array and get the values for the esdt_values
FOR esdt_value IN select * from unnest(v_esdt_pp)
LOOP
-- esdt_values as a key for the multi-dimensional arrays and also as the where clause value
SELECT distinct on ("SdsName") "SdsName" into v_sdsname from "Collection_ESDT_SDS_Def" where "ESDT" = esdt_values;
raise notice'esdt_value: %',esdt_value;
END LOOP;
Return ;
End
$$ Language plpgsql;
Select get_post_process_info(array['ab','bc]);
Your function sanitized:
CREATE OR REPLACE FUNCTION get_post_process_info(v_esdt_pp text[])
RETURNS SETOF record AS
$func$
DECLARE
esdt_value text;
v_sdsname text[];
v_dimension text[];
counter int := 1;
BEGIN
FOR esdt_value IN
SELECT * FROM unnest(v_esdt_pp) t
LOOP
SELECT distinct "SdsName" INTO v_sdsname
FROM "Collection_ESDT_SDS_Def"
WHERE "ESDT" = esdt_value;
RAISE NOTICE 'esdt_value: %', esdt_value;
END LOOP;
END
$func$ Language plpgsql;
Call:
Select get_post_process_info('{ab,bc}'::text[]);
DISTINCT instead of DISTINCT ON, missing table alias, formatting, some cruft, ...
Finally the immediate cause of the error: a missing quote in the call.
The whole shebang can possibly be replaced with a single SQL statement.
But, obviously, your function is incomplete. Nothing is returned yet. Information is missing.