Inserting string array using for loop in plpgsql - plpgsql

I am trying to insert data into a table using the two input strings. my proc has 2 character varying arrays as inputs. this is my for loop inside the proc.
The two inputs are
p_array character varying[]
u_array character varying[]
code inside proc.
FOREACH i IN ARRAY p_array
LOOP
--08/26/13 -> converted to merge
RAISE NOTICE 'Value of p_Id: %',p_array(i) ;
INSERT INTO p_map as d
(a_pkg,u_pkg)
VALUES(p_array(i),u_array(i))
ON CONFLICT (a_pkg) DO
UPDATE
SET
u_pkg = u_array(i);
END LOOP;
calling this procedure says success but the values are not inserted into the table. Am i doing anything wrong in for loop?.

Related

postgres plpgsql how to properly convert function to use FORMAT in DECLARE

I am writing a function in POSTGRES v13.3 that when passed an array of column names returns an array of JSONB objects each with the distinct values of one of the columns. I have an existing script that I wish to refactor using FORMAT in the declaration portion of the function.
The existing and working function looks like below. It is passed an array of columns and a dbase name. The a loop presents each column name to an EXECUTE statement that uses JSONB_AGG on the distinct values in the column, creates a JSONB object, and appends that to an array. The array is returned on completion. This is the function:
CREATE OR REPLACE FUNCTION foo1(text[], text)
RETURNS text[] as $$
declare
col text;
interim jsonb;
temp jsonb;
y jsonb[];
begin
foreach col in array $1
loop
execute
'select jsonb_agg(distinct '|| col ||') from ' || $2 into interim;
temp := jsonb_build_object(col, interim);
y := array_append(y,temp);
end loop;
return y;
end;
$$ LANGUAGE plpgsql;
I have refactored the function to the following. The script is now in the DECLARE portion of the function.
CREATE OR REPLACE FUNCTION foo2(_cols text[], _db text)
RETURNS jsonb[]
LANGUAGE plpgsql as
$func$
DECLARE
_script text := format(
'select jsonb_agg( distinct $1) from %1$I', _db
);
col text;
interim jsonb;
temp jsonb;
y jsonb[];
BEGIN
foreach col in array _cols
loop
EXECUTE _script USING col INTO interim;
temp := jsonb_build_object(col, interim);
y := array_append(y,temp);
end loop;
return y;
END
$func$;
Unfortunately the two functions give different results on a toy data set (see bottom):
Original: {"{\"id\": [1, 2, 3]}","{\"val\": [1, 2]}"}
Refactored: {{"id": ["id"]},{"val": ["val"]}}
Here is a db<>fiddle of the preceding.
The challenge is in the EXECUTE. In the first instance the col argument is treated as a column identifier. In the refactored function it seems to be treated as just a text string. I think my approach is consistent with the docs and tutorials (example), and the answer from this forum here and the links included therein. I have tried playing around with combinations of ", ', and || but those were unsuccessful and don't make sense in a format statement.
Where should I be looking for the error in my use of FORMAT?
NOTE 1: From the docs I have so possibly the jsonagg() and distinct are what's preventing the behaviour I want:
Another restriction on parameter symbols is that they only work in SELECT, INSERT, UPDATE, and DELETE commands. In other statement types (generically called utility statements), you must insert values textually even if they are just data values.
TOY DATA SET:
drop table if exists example;
create temporary table example(id int, str text, val integer);
insert into example values
(1, 'a', 1),
(2, 'a', 2),
(3, 'b', 2);
https://www.postgresql.org/docs/14/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
The command string can use parameter values, which are referenced in
the command as $1, $2, etc. These symbols refer to values supplied in
the USING clause.
What you want is paramterize sql identifier(column name).
You cannot do that. Access column using variable instead of explicit column name
Which means that select jsonb_agg( distinct $1) from %1$I In here "$1" must be %I type. USING Expression in the manual (EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];) will pass the literal value to it. But it's valid because select distinct 'hello world' from validtable is valid.
select jsonb_agg( distinct $1) from %1$I In here $1 must be same type as %1$I namely-> sql identifier.
--
Based on the following debug code, then you can solve your problem:
CREATE OR REPLACE FUNCTION foo2(_cols text[], _db text)
RETURNS void
LANGUAGE plpgsql as
$func$
DECLARE
col text;
interim jsonb;temp jsonb; y jsonb[];
BEGIN
foreach col in array _cols
loop
EXECUTE format( 'select jsonb_agg( distinct ( %1I ) ) from %2I', col,_db) INTO interim;
raise info 'interim: %', interim;
temp := jsonb_build_object(col, interim);
raise info 'temp: %', temp;
y := array_append(y,temp);
raise info 'y: %',y;
end loop;
END
$func$;

How to call decode function on column before insert statement?

I have a table and some of the columns are "bytea" type. What I want to do is; Before any insert statement check if a column type is "bytea" then decode hex value of column value.
Is there a way to create a trigger function like below?
INSERT INTO USERS (ID, NAME, STATUS) VALUES ('0x3BEDDASTSFSFSDS', 'test', 'new')
to
INSERT INTO USERS (ID, NAME, STATUS) VALUES (decode('0x3BEDDASTSFSFSDS', 'hex'), 'test', 'new')
There is no way to intercept the input string before Postgres parses it as a bytea.
It'll be read as an "escape" format literal, i.e. the bytea will end up holding the hex digits' ASCII codepoints. You can undo this, though it's a little unpleasant:
create function trg() returns trigger language plpgsql as $$
begin
assert substring(new.id from 1 for 2) = '0x';
new.id = decode(convert_from(substring(new.id from 3), current_setting('server_encoding')), 'hex');
return new;
end
$$;
create trigger trg
before insert on users
for each row
execute procedure trg();
If you have any control over the input, just change the 0x to \x, and Postgres will convert it for you.

String Manipulation throws error while Inserting into PostgreSql Jsonb Column

I have the following code, to insert data into my table res(ID bigserial,
Results jsonb not null). I want to insert data so that 'Display' column always has the 'i' appended to it, so that every row has a different value for 'Display' Column.
DO $$
declare cnt bigint;
BEGIN
FOR i IN 1..2 LOOP
INSERT INTO res (Results)
VALUES ('{"cid":"CID1","Display":"User One'|| i || '","FName":"Userfff","LName":"One"}');
INSERT INTO res (Results)
VALUES ('{"cid":"CID1","Display":"User One'|| i || '","FName":"Userfff","LName":"One"}');
INSERT INTO res (Results)
VALUES ('{"cid":"CID1","Display":"User One'|| i || '","FName":"Userfff","LName":"One"}');
END LOOP;
END;
$$
LANGUAGE plpgsql;
However, when I run this code, I get the following error:
ERROR: column "results" is of type jsonb but expression is of type text
LINE 2: VALUES ('{"cid":"CID1","Display":"User One'|| i ...
^
HINT: You will need to rewrite or cast the expression.
How should I modify my query so that the query runs successfully?
No need for a loop or even PL/pgSQL.
You can generate any number of rows using generate_series()
To put a value into a string I prefer to use format() as that makes dealing with strings a lot easier:
insert into res(results)
select format('{"cid":"CID1","Display":"User One %s","FName":"Userfff","LName":"One"}', i::text)::jsonb
from generate_series(1,5) g(i);
The second parameter to the format() function will be put where the %s in the first parameter.
The above inserts 5 rows each with a different value for display. This is what you stated in your question. Your sample code however inserts a total of 6 rows where 3 rows have the same value for display.

Selecting from multidimensional array parameter in Postgres

I have a function that does an insert in a parent table then inserts into a child table. The last parameter is an array of variable length. I use this to do the insert into the child table by sending an array of comma delimited pairs and parsing these out.
create or replace function neil_test(time_stamp timestamp with time zone, type_id text, raw_message text,field_values text[])
returns void
AS $$
DECLARE
last_message_id bigint;
x text;
BEGIN
INSERT INTO message(time_stamp,type_id,raw_message) values(time_stamp,type_id,raw_message);
select into last_message_id currval(pg_get_serial_sequence('message', 'id'));
foreach x in ARRAY field_values
LOOP
insert into message_field_value(last_message_id,field_id,fieldValue) select left(x,strpos(x,',')-1), right(x,length(x)-strpos(x,','));
END LOOP;
END
$$LANGUAGE plpgsql
It's called like this:
select neil_test('2001-01-01 08:00:00.1234','F','RAW',ARRAY['One,value1','two,value2','three,value3'])
This works OK but what I'd really like to do is use the array directly. Something like :
select neil_test('2001-01-01 08:00:00.1234','F',
'RAW',ARRAY[['One', 'value1'],['two','value2'],['three','value3']])
...
insert into message_field_value(last_message_id,fieldid, field value)
select field_values[1], field_values[2]
I've tried the unnest function but this doesn't work as it seems to flatten out the whole array and I lose the pairs. Is something like this even possible with Postgres arrays?
Use SLICE in the FOREACH statement to iterate over elements of multidimencional array. Without SLICE it iterates over individual elements of the array. This works for me:
CREATE OR REPLACE FUNCTION neil_test2(time_stamp timestamp with time zone, type_id text, raw_message text,field_values text[][2])
RETURNS void
AS $$
DECLARE
last_message_id bigint;
x TEXT[];
BEGIN
INSERT INTO message(time_stamp,type_id,raw_message) VALUES (time_stamp,type_id,raw_message);
SELECT INTO last_message_id currval(pg_get_serial_sequence('message', 'id'));
FOREACH x SLICE 1 in ARRAY field_values LOOP
INSERT INTO message_field_value(last_message_id, field_id, field_value) SELECT last_message_id, x[1], x[2];
END LOOP;
END
$$LANGUAGE plpgsql;

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;