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

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)"}

Related

How to use FOREACH in a PostgreSQL LOOP

I am trying to write a very simple pgsql statement to loop through a simple array of state abbreviations.
CREATE OR REPLACE FUNCTION my_schema.showState()
RETURNS text AS
$$
DECLARE
my_array text[] := '["az","al", "ak", "ar"]'
BEGIN
FOREACH state IN my_array
LOOP
RETURN SELECT format('%s', state);
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM showState();
I am using PostgresSQL version 11+. I keep getting an error ERROR: syntax error at or near "BEGIN" The output I want to see here is just seeing the state abbreviation printed in the results window for now. Like this:
What am I doing wrong here?
There's a ; missing after my_array text[] := '["az","al", "ak", "ar"]'.
'["az","al", "ak", "ar"]' isn't a valid array literal.
If you want a set returning function, you need to declare its return type as a SETOF.
The ARRAY keyword is missing in the FOREACH's head.
state must be declared.
You need to use RETURN NEXT ... to push a value into the set to be returned.
format() is pointless here, it doesn't effectively do anything.
With all that rectified one'd get something along the lines of:
CREATE
OR REPLACE FUNCTION showstate()
RETURNS SETOF text
AS
$$
DECLARE
my_array text[] := ARRAY['az',
'al',
'ak',
'ar'];
state text;
BEGIN
FOREACH state IN ARRAY my_array
LOOP
RETURN NEXT state;
END LOOP;
END;
$$
LANGUAGE plpgsql;
db<>fiddle

Postgres split and convert string value to composite array

I have a Type with below structure.
create type t_attr as (id character varying(50), data character varying(100));
I get text/varchar value through a user-defined function. The value looks like below.
txt := 'id1#data1' , 'id2#data2';
(In the above example, there are 2 values separated by comma, but the count will vary).
I'm trying to store each set into the t_attr array using the below code. Since each set will contain a # as separator I use it to split the id and data values. For testing purpose I have used only one set below.
DO
$$
DECLARE
attr_array t_attr[];
txt text := 'id1#data1';
BEGIN
attr_array[1] := regexp_split_to_array(txt, '#');
END;
$$
LANGUAGE plpgsql;
But the above code throws error saying 'malformed record literal' and 'missing left paranthesis'.
Can someone please help on how to store this data into array? Thanks.
It is because you are trying to assign an array value to the record. Try:
do $$
declare
attr_array t_attr[];
txt text := 'id1#data1';
begin
attr_array[1] := (split_part(txt, '#', 1), split_part(txt, '#', 2));
raise info '%', attr_array;
end $$;
Output:
INFO: {"(id1,data1)"}
DO
However if you really need to split values using arrays:
do $$
declare
a text[];
begin
....
a := regexp_split_to_array(txt, '#');
attr_array[1] := (a[1], a[2]);
end $$;

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 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$;

Composite Array Type as OUTPUT parameter in PostgreSQL

I'm trying to insert data into composite array type in my function. It should accept data from composite array type of INPUT parameter and store data into OUPUT parameter of same type.
CREATE TYPE public.type_x_type AS (x integer);
CREATE TYPE public.type_y_type AS(x integer,y integer);
My function is
CREATE OR REPLACE FUNCTION GET_PRICE_PC_X
(
IP_PRICE_INFO IN TYPE_X_TYPE[],
PC_COST OUT TYPE_Y_TYPE[],
OP_RESP_CODE OUT VARCHAR,
OP_RESP_MSG OUT VARCHAR
)
RETURNS RECORD AS $$
DECLARE
SELECTED_PRICE CURSOR(IP_PFCNTR INT)
FOR
SELECT ID, PHONE FROM CUSTOMER WHERE ID=IP_PFCNTR;
J NUMERIC(10);
BEGIN
J := 0;
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := ROW(K.ID,K.PHONE);
END LOOP;
END LOOP;
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$$ language 'plpgsql';
select * from GET_PRICE_PC_X(ARRAY[ROW(1)] :: TYPE_X_TYPE[]);
And I'm getting the below error.
PC_COST | OP_RESPONSE_CODE | OP_RESP_MSG
---------------------------------------------------------
| 200 | malformed array literal: "(1,30003)"
I'll be calling that OUT type somewhere, so I need the data to be inserted into array.
When you develop a function, then doesn't use WHEN OTHERS. The debugging is terrible then. The problem of your function is a assignment a composite type to a array
PC_COST := ROW(K.ID,K.PHONE);
This is wrong. Probably you would to do append.
The critical part should to look like
J := 0; PC_COST := '{}';
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := PC_COST || ROW(K.ID,K.PHONE)::type_y_type;
END LOOP;
END LOOP;
Your function can be replaced by one query - maybe less readable, but significantly faster - loops with nested queries can be slow (is faster run one simple SELECT than more trivial SELECTs):
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[],
OUT pc_cost type_y_type[],
OUT op_resp_code character varying,
OUT op_resp_msg character varying)
RETURNS record
LANGUAGE plpgsql STABLE
AS $function$
BEGIN
pc_cost := ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$function$;
Note: using NUMERIC type for cycle variable is a wrong idea - this type is expensive and should be used only for money or precious calculation. The int type is absolutely correct in this place.
Usually you can reduce you function more - the error handling should not be there - there is not a reason why this function should fail - (not a reason that can be handled)
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[])
RETURNS type_y_type[]
LANGUAGE sql STABLE
AS $function$
SELECT ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
$function$;