Assign Element To Index of Array with Attribute in PostgreSSQL - arrays

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.

Related

How to return array of values from PostgreSQL function by INT id

I am trying to create a simple PostgreSQL function, where by using INT parameter I like to get array back. The example below will not work, but shall give idea of what I try to get back from a function. Thanks.
CREATE OR REPLACE FUNCTION contact_countries_array(INT)
RETURNS ANYARRAY AS '
SELECT ARRAY[contacts_primarycountry, contacts_othercountry] FROM contacts WHERE contacts_id = $1'
LANGUAGE SQL;
The data type of contacts_primarycountry and contacts_othercountry is integer. contacts_id is unique and integer.
Per the docs:
It is permitted to have polymorphic arguments with a fixed return
type, but the converse is not.
As such, I think your attempt to return anyarray won't work.
Your fields look like text, so I think if you altered it to something like this, it would work:
CREATE OR REPLACE FUNCTION contact_countries_array(INT)
RETURNS text[] AS $$
select array[contacts_primarycountry::text, contacts_othercountry::text]
FROM contacts WHERE contacts_id = $1
$$
LANGUAGE SQL;
This should compile, and it might work, but I'm honestly not sure:
CREATE OR REPLACE FUNCTION contact_countries_array(anyelement)
RETURNS anyarray AS $$
select array[contacts_primarycountry::text, contacts_othercountry::text]
FROM contacts WHERE contacts_id = $1
$$
LANGUAGE SQL;
I think the datatypes would have to match perfectly for this to work, unless you did casting.
Declaring Array, Looping, Adding items to Array, Returning Array with Postgres Function,
You can declare INTEGER array instead of TEXT and avoid casting (counter::TEXT) as well as return type TEXT[]. (Added those for reference.)
CREATE OR REPLACE FUNCTION "GetNumbers"(maxNo INTEGER) RETURNS TEXT[] AS $nums$
DECLARE
counter INTEGER := 0;
nums TEXT[] := ARRAY[]::TEXT[];
BEGIN
LOOP
EXIT WHEN counter = maxNo;
counter = counter + 1;
nums = array_append(nums, counter::TEXT);
END LOOP;
RETURN nums;
END ;
$nums$ LANGUAGE plpgsql;
SELECT "GetNumbers"(5); -- {1,2,3,4,5}

How to use dbms_output to display an array of values assigned to a variable?

I have something like this, but got an error says ORA-06533: Subscript beyond count. I want to see all the values from the "select distinct" statement in the output tab. Anyone can help? thanks!
DECLARE
TYPE v_chks_array IS VARRAY (10) OF VARCHAR2 (50);
arrSRCs v_chks_array;
BEGIN
arrSRCs := v_chks_array ();
arrSRCs.EXTEND (10);
SELECT /*+parallel (a,4)*/
DISTINCT a.src_table
BULK COLLECT INTO arrSRCs
FROM hcr_dm.hcr_dm_fact a;
DBMS_OUTPUT.put_line (arrSRCs (10));
END;
Collections are not needed here:
begin
for results in
(
select /*+ parallel (a,4) */ distinct a.src_table
from hcr_dm.hcr_dm_fact a;
) loop
dbms_output.put_line(results.src_table);
end loop;
end;
/
Instead of VARRAY, you may try a TABLE type collection. You'll have more flexibility with number of records to hold while doing BULK COLLECT.
Create or replace type v_chks_array IS TABLE OF VARCHAR2 (500);
More information can be found at http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/collections.htm

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

Postgresql save query result in 2d array

I would like to create 2d array inside a function and populate it with values from select statement. I try this code and end up with one-dimension array. What am I doing wrong?
select array(select a from t a)
=====================================
"{"(1,stxt,varchar)","(2,sint,int)"}"
create or replace function __test(
) returns text
language 'plpgsql' as
$$
declare
_dat varchar[][];
begin
_dat = (select array(select a from t a));
return array_dims(_dat);
end;
$$;
select __test();
===========
"[1:2]"
I expected last command to return [1:2][1:3] for two rows of three columns.
PostgreSQL support only one type arrays - so you can take only array of row / you can't to take 2D array what you would. There are no way how to do it well now.

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