Unnesting an array of arrays, to a simple array in plpgsql - postgresql

This doesn't work:
DO
$do$
DECLARE
nested varchar[][] := '{{a},{b},{c}}';
unnested varchar[];
BEGIN
unnested := unnest(nested)::text[];
END
$do$;
Because, it seems, the unnest expression returns a table. The error message is:
[22P02] ERROR: malformed array literal: "a" Detail: Array value must
start with "{" or dimension information. Where: SQL statement "SELECT
unnest(nested)::text[]" PL/pgSQL function inline_code_block line 7 at
assignment
So I guess the solution is to create an array out of the unnest return value? How do I do this?

You can't cast the result of a set returning function to an array.
DO
$do$
DECLARE
nested varchar[][] := '{{a},{b},{c}}';
unnested varchar[];
BEGIN
unnested := array(select * from unnest(nested));
END
$do$;

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

An error related to arrays in postgresql "ERROR: malformed record literal:"

I am trying to get values from a table into an array and the array definition is different from the table's column definition. I have tried do cast but it is not working. Basically I need as an array (tab_small_str) the values in the table. Can someone please suggest on it:
CREATE TYPE tab_small_str AS (
str CHARACTER VARYING(50)
);
create table test_emp(emp_id integer, ename character varying (10));
insert into test_emp values(1,'a1')
insert into test_emp values(2,'a2')
insert into test_emp values(3,'a3')
CREATE OR REPLACE function test_fn () RETURNS VARCHAR[] as
$$
DECLARE
v_ename tab_small_str[];
i tab_small_str;
BEGIN
SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp) INTO v_ename;
RAISE INFO 'array is: %',v_ename;
RETURN v_ename;
FOREACH i IN ARRAY v_ename
LOOP
RAISE info 'value of ename is%', i;
END LOOP;
END;
$$
language plpgsql;
(function compiles fine).
select test_fn()
--gives below error
ERROR: malformed record literal: "a1"
DETAIL: Missing left parenthesis.
CONTEXT: SQL statement "SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp)"
PL/pgSQL function test_fn() line 7 at SQL statement
********** Error **********
ERROR: malformed record literal: "a1"
SQL state: 22P02
Detail: Missing left parenthesis.
Context: SQL statement "SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp)"
PL/pgSQL function test_fn() line 7 at SQL statement
Hi 404,
i modified as suggested:
CREATE OR REPLACE function test_fn () RETURNS tab_small_str[] as
$$
DECLARE
v_ename tab_small_str[];
i tab_small_str;
BEGIN
SELECT ARRAY(SELECT ROW(ename)::tab_small_str FROM test_emp) INTO v_ename;
RAISE INFO '%',v_ename;
FOREACH i IN ARRAY v_ename
LOOP
RAISE NOTICE '%', i;
END LOOP;
RETURN v_ename;
END;
$$
language plpgsql;
it returns output as:
INFO: {(a1),(a2),(a3)}
CONTEXT: PL/pgSQL function test_fn() line 9 at RAISE
NOTICE: (a1)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
NOTICE: (a2)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
NOTICE: (a3)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
My question is why the output is surrounded by bracket - why not just a1 but (a1). Can you please suggest on it?
Your new type is not a "single field data type", for want of a better description, where you can cast something like a VARCHAR(10) directly to it; it's a ROW containing a single field. So something like 'blah'::tab_small_str fails because it's trying to cast that text to the type which contains a field, rather than the field itself.
To resolve, using your existing query:
SELECT ename::tab_small_str FROM test_emp
Change to:
SELECT ROW(ename)::tab_small_str FROM test_emp
As to why your results are surrounded by brackets: that is how a ROW or composite type is displayed when shown as a single field (or, non-expanded): for example, if you do SELECT * FROM test_emp, the * returns all fields individually as separate columns; however if you do SELECT test_emp FROM test_emp, that will return the table row unexpanded, so it will look like so:
(1,a1)
(2,a2)
(3,a3)
And composite types are exactly the same. i tab_small_str; - think of i as test_emp, which contains fields which can be expanded. In your code you are printing the object i, rather than i.* or i.str. So change your code to:
FOREACH i IN ARRAY v_ename
LOOP
RAISE NOTICE '%', i.str;
END LOOP;

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

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.

INSERT while LOOPing

i'd like to insert some records into a table while looping. Records are passed as a varchar tab to a function ie.:
create function a(tab varchar[]) RETURNS void AS
$$
DECLARE
b varchar(20);
BEGIN
FOREACH b IN ARRAY tab
LOOP
INSERT INTO....;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
However, when executing there is the error:
ERROR: syntax error at or near "FOREACH"
LINE 1: FOREACH $1 IN ARRAY tab LOOP INSERT INTO x (c, ...
QUERY: FOREACH $1 IN ARRAY tab LOOP INSERT INTO x (c) VALUES ( $1)
CONTEXT: SQL statement in PL/PgSQL function " near line ...
********** ERROR **********
ERROR: syntax error at or near "FOREACH"
Does anybody know why and how to fix this?
FOREACH construct was added in 9.1. For earlier versions you can use unnest() function the following way:
CREATE FUNCTION a(tab varchar[]) RETURNS void AS
$$
DECLARE
b varchar(20);
BEGIN
FOR b IN SELECT unnest(tab)
LOOP
INSERT INTO x(c) VALUES (b);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
Since PostgreSQL 9.1 there is also a built-in way to loop through array slices:
FOREACH x SLICE 1 IN ARRAY $1
LOOP
RAISE NOTICE 'row = %', x;
END LOOP;
Where x must be a matching array type and ...
The SLICE value must be an integer constant not larger than the number of dimensions of the array
For 1-dimensional arrays just omit the SLICE part and x can be a simple type like you display in your question.
I want to add one thing, and that is that unnest unnests all levels of an array:
select * from unnest(ARRAY[ARRAY[1,2,3],Array[5,6,7]]);
unnest
--------
1
2
3
5
6
7
(6 rows)
If you want to loop through one level of a multi-dimensional array, you should loop through the following instead
FOR out_count IN
array_lower(in_transactions, 1) ..
array_upper(in_transactions, 1)
LOOP
-- Fill the bulk payments table
INSERT INTO bulk_payments_in(id, amount)
VALUES (in_transactions[out_count][1],
in_transactions[out_count][2]);
END LOOP;