plpgsql function no results - arrays

I have done the following function in plpgsql. It works fine and the it returns 'OK', BUT for some reason it inputs nothing in the table temp.
CREATE OR REPLACE FUNCTION public.get_route(node integer[])
RETURNS character varying AS
$BODY$DECLARE
i int := 0;
_r record;
vertex int;
BEGIN
FOREACH i IN ARRAY node
LOOP
IF( i < (array_length(node, 1) - 1))
THEN
FOR _r IN EXECUTE 'select * from shortest_path(''
select id as id, source_id::int4 as source, target_id::int4 as target, cost, reverse_cost
from network_of_point'','|| node[i] ||','|| node[i+1] ||', true, true)'
LOOP
vertex := _r.vertex_id;
EXECUTE 'insert into temp
select nextval(''road_intersection_id_seq''), point
from distinct_network_point
where distinct_network_point.id ='|| vertex;
END LOOP;
i = i + 1;
END IF;
END LOOP;
RETURN 'OK';
END;$BODY$
The following is the synatx I am using to call the function:
select get_route('{2,7}')

It doesn't work because this line
IF( i < (array_length(node, 1) - 1))
You're testing if 2 or 7 (taken from your example) are less than the length of the array, that's valued 2.
That's because your next queries are never executed.

Because of this line, inner part of code never executed.
IF( i < (array_length(node, 1) - 1))
Actually you are extracting value of i from node array
FOREACH i IN ARRAY node
which has value 2 or 7 as in your example and you are treating i as node index which is wrong. I have created a separate int variable value, now we can use i as array index.
I think you want to achieve this.
CREATE OR REPLACE FUNCTION public.get_route(node integer[])
RETURNS character varying AS
$BODY$
DECLARE
i int := 1;
_r record;
vertex int;
value int;
BEGIN
FOREACH value IN ARRAY node
LOOP
IF( i < (array_length(node, 1)))
THEN
FOR _r IN EXECUTE 'select * from shortest_path(''
select id as id, source_id::int4 as source, target_id::int4 as target, cost, reverse_cost
from network_of_point'','|| node[i] ||','|| node[i+1] ||', true, true)'
LOOP
vertex := _r.vertex_id;
EXECUTE 'insert into temp
select nextval(''road_intersection_id_seq''), point
from distinct_network_point
where distinct_network_point.id ='|| vertex;
END LOOP;
i = i + 1;
END IF;
END LOOP;
RETURN 'OK';
END;
$BODY$ language plpgsql;

Related

Assign Element To Index of Array with Attribute in PostgreSSQL

Can I assign a value from cursor to index of array with attribute?
I have an oracle code like this :
cursor cursor1 is select name, value from table;
FOR loop1 IN cursor1 LOOP
array1.EXTEND;
array1(loop1).cur_name := cursor1.name;
array1(loop1).cur_value := cursor1.value;
END LOOP;
i tried to convert to postgresql like this, but it's getting error
CREATE FUNCTION function_name () RETURNS something AS $$
DECLARE
cursor1 cursor for select name, value from table;
array1 text[];
BEGIN
-- Do something
...
FOR loop1 IN cursor1 LOOP
array1[loop].cur_name := cursor1.name; --error here
array1[loop1].cur_value := cursor1.value; -- error here
END LOOP;
-- Do something
...
RETURN;
END;
is there any method to create an array with attibute name?
The Oracle function is returning a collection (An Associative Array if I remember correctly, but its been awhile). Postgres does NOT have collections, the closest data type is an array. However since your collection contains multiple columns, you need to create a UDT (user defined type}, then your function returns an array of that type. (Note I assumed the data types in the table. Correct as deeded.)
create type name_val as (name text, value integer);
create or replace function function_name ()
returns name_val[]
language plpgsql
as $$
declare
cursor1 cursor for
select name, value
from test
limit 10;
rec record;
array1 name_val[];
l_name_val name_val;
begin
-- do something
for rec in cursor1
loop
l_name_val.name = rec.name;
l_name_val.value = rec.value;
array1 = array1 || l_name_val;
end loop;
-- do something
return array1;
end;
$$;
There are a couple other option which avoid the cursor and looping altogether. Assuming you actually need any Array returned you can reduce the above function to a single sql statement:
create or replace function function_name3()
returns name_val[]
language sql
as $$
select array_agg((name, value)::name_val)
from test
limit 10;
$$;
Demo Here
UPDATE:
I noticed that subsequent to my answer you update the question from for loop1 in 1 .. 10 ... to for rec in cursor1 ... thus removing the resulting row limitation. You accomplish the same by just removing the Limit 10 clause.

PostgreSQL C function to get values

I am trying to write a PostgreSQL function in C.
My goal is finding minimum value of a list. So, my function will be executed like these:
SELECT min_to_max(val) FROM (VALUES(1),(2),(3)) x(val);
SELECT min_to_max(val) FROM my_table;
Here is my C code and I lost here. For example, there is a function called "PG_GETARG_INT32" to get integer values, but I don' t know how to get values from a table in order to process. Any idea?
#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(get_sum);
Datum
get_sum(PG_FUNCTION_ARGS)
{
ArrayType *v1,
bool isnull;
isnull = PG_ARGISNULL(0);
if (isnull)
ereport( ERROR,
( errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("The input cannot be empty")));
List a = PG_GETARR_SOMEFUNCTION_2_GET_LIST(0);
# for loop iteration to find min_val
# return min_val
}
Edited(2022.05.13 - below is edited part):
Thanks to #Laurenz Albe. I made some progress.
Yet, now I want to go further(No needs to be in C language. As Laurenz Albe stated, I am just taking small steps).
My functions and aggregates like below to find min and max:
CREATE or replace FUNCTION find_min_func(
state integer,
next integer
) RETURNS integer
LANGUAGE plpgsql
STRICT
AS $$
declare
min_val integer;
begin
if $1 <= $2 then min_val := $1;
elsif $2 <$1 then min_val := $2;
end if;
return min_val;
END;
$$;
CREATE or replace AGGREGATE find_min(integer)
(
SFUNC = find_min_func,
STYPE = integer
);
CREATE or replace FUNCTION find_max_func(
state integer,
next integer
) RETURNS integer
LANGUAGE plpgsql
STRICT
AS $$
declare
max_val integer;
begin
if $1 >= $2 then max_val := $1;
elsif $2 > $1 then max_val := $2;
end if;
return max_val;
END;
$$;
CREATE or replace AGGREGATE find_max(integer)
(
SFUNC = find_max_func, -- State function
STYPE = integer -- State type
);
They are working great but now I want to do something like
SELECT min_to_max(val) FROM (VALUES(1),(2),(3)) x(val);
Expected output:
1 -> 3
So, I just wrote a state function and aggregate pair like below(I know it is wrong):
CREATE or replace FUNCTION find_min_and_max_func(
state integer,
next integer
) RETURNS varchar
LANGUAGE plpgsql
STRICT
AS $$
declare
min_val integer;
max_val integer;
output varchar;
begin
if $1 <= $2 then min_val := $1; max_val := $2;
elsif $2 <$1 then min_val := $2; max_val := $1;
end if;
output = cast(min_val as varchar) || '->' || cast(max_val as varchar) ;
return output;
END;
$$;
CREATE or replace AGGREGATE find_min_and_max(integer)
(
SFUNC = find_min_and_max_func, -- State function
STYPE = varchar -- State type
);
It is wrong because state function is taking arguments as integer but returns(?) varchar, so it varying.
How can I arrange my state function here?
Thanks!
To create an aggregate function, you have to use CREATE AGGREGATE. You create that in SQL. What you may need to implement in C is the state transition function (SFUNC), perhaps also others, depending on the kind of aggregate you want to create.
The aggregate function does not have to read from the table; the PostgreSQL executor will feed it the data it needs.
If you start writing C functions, you should perhaps start with something simpler than aggregate functions.
With the help of #Laurenz Albe(thanks to him again), I found the solution. Also, I checked out:
https://hoverbear.org/blog/postgresql-aggregates-with-rust/
https://hashrocket.com/blog/posts/custom-aggregates-in-postgresql
Here is my solution:
CREATE or replace FUNCTION find_min_and_max_func(
state point,
next integer
) RETURNS point
LANGUAGE plpgsql
STRICT
AS $$
declare
min_val integer;
max_val integer;
begin
if state[0] <= next then min_val := state[0];
elsif next < state[0] then min_val := next;
end if;
if state[1] >= next then max_val := state[1];
elsif next > state[1] then max_val := next;
end if;
return point(min_val, max_val) ;
END;
$$;
CREATE or replace FUNCTION find_min_and_max_final_func(
state point
) RETURNS varchar
LANGUAGE plpgsql
STRICT
AS $$
begin
return cast(state[0] as varchar) || '->' || cast(state[1] as varchar) ;
END;
$$;
CREATE or replace AGGREGATE find_min_and_max(integer)
(
SFUNC = find_min_and_max_func, -- State function
STYPE = point, -- State type
FINALFUNC = find_min_and_max_final_func,
initcond = '(1231231232131,0)'
);
SELECT find_min_and_max(value) FROM UNNEST(ARRAY [1, 2, 3]) as value;
find_min_and_max
------------------
1->6
(1 row)
Thanks!

How to loop through string[] in postgresql?

In this postgressql function i created a array by spliting a string. Now i want loop on this array and do some processing on it.
Function:
CREATE OR REPLACE FUNCTION getAllFoo() RETURNS character varying as
$BODY$
DECLARE
arr_split_data text[];
counter character varying;
begin
counter := ''; -- Init value
-- split data. Add in array
select into arr_split_data regexp_split_to_array('a,b,c,d,e,f',',');
FOR r IN arr_split_data -- error
LOOP
counter := arr_split_data[r] || '_' || counter; -- do some processing
END LOOP;
return counter;
END
$BODY$
LANGUAGE 'plpgsql';
But I am getting this error
when I execute this function. Is my syntax for loop is wrong?
The syntax is
FOREACH r IN ARRAY arr_split_data
LOOP
counter := r || '_' || counter;
-- do some processing
END LOOP;
You'll need to declare r too:
DECLARE
arr_split_data TEXT [];
r CHARACTER VARYING;
counter CHARACTER VARYING;
BEGIN
See section 41.6.5 of the manual: Looping Through Arrays
CREATE OR REPLACE FUNCTION getAllFoo() RETURNS character varying as
$BODY$
DECLARE
r character varying;
arr_split_data text[];
counter character varying;
begin
counter := ''; -- Init value
-- split data. Add in array
select into arr_split_data regexp_split_to_array('a,b,c,d,e,f',',');
FOREACH r IN array arr_split_data LOOP
counter := counter || '_' || r; -- do some processing
END LOOP;
return counter;
END
$BODY$
LANGUAGE 'plpgsql';

Error passing array of ints to a plpgsql function (postgres 8.3.7)

I have the following function, which receives an array of ints, iterates it calling other function:
CREATE FUNCTION calculateAbsencesForIDs(INT[]) RETURNS TEXT AS
'
DECLARE
index integer := 0;
resultArray decimal[];
id int;
result text;
BEGIN
FOR id IN SELECT $1 LOOP
resultArray[index] = calculateAbsenceForID(id);
index := index + 1;
END LOOP;
RETURN array_to_string(resultArray, result);
END;
'
LANGUAGE plpgsql;
I try to call it using:
SELECT calculateAbsencesForIDs(ARRAY[85,74,75,76,77,78,79,80]);
or
SELECT calculateAbsencesForIDs('{85,74,75,76,77,78,79,80}');
or
SELECT calculateAbsencesForIDs('{85,74,75,76,77,78,79,80}'::int[]);
...
But I have always the same error:
[Error Code: 0, SQL State: 22P02] ERROR: invalid input syntax for integer: "{85,74,75,76,77,78,79,80}"
I don't know how I can call this function. I have looked in postgres docs, I think this is correct but it doesn`t work.
This line:
FOR id IN SELECT $1 LOOP
means that you assign id to each value from SELECT $1 which returns a single record with a single field of type INT[].
As id is declared as INT, you get the conversion error you're observing.
In 8.4 and above you could use UNNEST, in 8.3 replace it with
FOR id IN
SELECT $1[i]
FROM generate_series(1, ARRAY_UPPER($1, 1)) i
LOOP
Alternatively, you could just do:
SELECT ARRAY_TO_STRING
(
ARRAY
(
SELECT calculateAbsenceForID($1[i])
FROM generate_series(1, ARRAY_UPPER($1, 1)) i
)
)
You need to loop over the elements of the array like this:
BEGIN
FOR i in 1 .. array_length($1, 1) LOOP
resultArray[i] = calculateAbsenceForID($1[i]);
END LOOP;
RETURN array_to_string(resultArray, result);
END;
Note that this will throw an error if $1 is NULL

How to apply a function to each element of an array column in Postgres?

A Pg query returns an array. I would like to retrieve that with each element formatted to 3 decimal places. How can I apply a function to each element of an array? Something like the following (wrong, obviously) --
SELECT Round(ARRAY[1.53224,0.23411234], 2);
{1.532, 0.234}
I guess I am looking for something like Perl's map function.
First, turn the array into a set using unnest:
> SELECT n FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
n
------------
1.53224
0.23411234
(2 rows)
Then, apply an expression to the column:
> SELECT ROUND(n, 2) FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
round
-------
1.53
0.23
(2 rows)
Finally, use array_agg to turn the set back into an array:
> SELECT array_agg(ROUND(n, 2)) FROM unnest(ARRAY[1.53224,0.23411234]) AS n;
array_agg
-------------
{1.53,0.23}
(1 row)
postgres=# select array(select round(unnest(array[1.2,2.4,3,4])));
array
-----------
{1,2,3,4}
(1 row)
You may need to create a stored function. Here is the one that does what you need:
CREATE OR REPLACE FUNCTION array_round(float[], int)
RETURNS float[]
AS
$$
DECLARE
arrFloats ALIAS FOR $1;
roundParam ALIAS FOR $2;
retVal float[];
BEGIN
FOR I IN array_lower(arrFloats, 1)..array_upper(arrFloats, 1) LOOP
retVal[I] := round(CAST(arrFloats[I] as numeric), roundParam);
END LOOP;
RETURN retVal;
END;
$$
LANGUAGE plpgsql
STABLE
RETURNS NULL ON NULL INPUT;
Then call something like this:
# SELECT array_round(ARRAY[1.53224,0.23411234], 2);
array_round
-------------
{1.53,0.23}
(1 row)
You need to turn the array into a row set. For example, using generate_series:
SELECT ARRAY(SELECT ROUND(ARRAY[1.53224,0.23411234])[i], 2) FROM generate_series(1,2) AS s(i));
I know that's pretty ugly. There should be a helper function to make such mappings easier.
Perhaps something like (yes it's horrible, slow, and brittle dynamic code):
CREATE OR REPLACE FUNCTION map_with_arg(TEXT, ANYARRAY, TEXT)
RETURNS ANYARRAY
IMMUTABLE STRICT
LANGUAGE 'plpgsql' AS
$$
DECLARE
i INTEGER;
t TEXT;
cmd TEXT;
BEGIN
FOR i IN array_lower($2, 1) .. array_upper($2, 1) LOOP
cmd := 'SELECT ('||quote_ident($1)||'('||quote_nullable($2[i])||', '||quote_nullable($3)||'))::TEXT';
EXECUTE cmd INTO t;
$2[i] := t;
END LOOP;
RETURN $2;
END;
$$;
select map_with_arg('repeat', array['can','to']::TEXT[], '2');
map_with_arg
---------------
{cancan,toto}
Update It occurs to me that we could use a single dynamic statement for the whole loop. This could mitigate some of the performance concerns.
CREATE OR REPLACE FUNCTION map_with_arg(TEXT, ANYARRAY, TEXT)
RETURNS ANYARRAY
IMMUTABLE STRICT
LANGUAGE 'plpgsql' AS
$$
DECLARE
cmd TEXT;
rv TEXT;
BEGIN
cmd := 'SELECT ARRAY(SELECT (' || quote_ident($1)||'($1[i], '||quote_nullable($3)||'))::TEXT FROM generate_subscripts($1, 1) AS gs(i))';
EXECUTE cmd USING $2 INTO rv;
RETURN rv;
END;
$$;

Resources