Postgresql VARADIC function, need help (beginner) - arrays

I´m new in programming with plpgsql and stored functions in postgresql.
I´ve programmed one function called this:
CREATE OR REPLACE FUNCTION public.uploadbutton(VARIADIC arr character varying[])
RETURNS text
LANGUAGE plpgsql
AS $function$DECLARE
arrayi ALIAS for $1;
result text;
begin
return result;
END;
$function$
;
As you can see, the function does not have many code. The problem is that i don´t really understand how to handle Arrays in Postgresql and plpgsql.
I need to split the array every 3 delimeters until the end of array, the array can have many inputs but minimum 3.
SELECT uploadbutton('59373033336415021231','5','a','59373033335915022fff','5','b')
Here an example for calling it.
The output should look like that:
('59373033336415021231','5','a')('59373033336415021231','5','a')
Can you teach me, how to handle that ?
Kind regards guys!
EDIT:
After getting this Result :
('59373033336415021231','5','a')('59373033336415021231','5','a')
I need to put every 3 parameters, and call a second function with the first part of the result as parameters. And the second Value have to be Int. This should look like this:
edit('59373033336415021231',5,'a')
and the same for the second part of the result
edit('59373033336415021231',5,'a')
I´m trying it at the moment and want to get the id of the first parameter with my edit() function. This is what i have:
CREATE OR REPLACE FUNCTION public.upload(string_in text)
RETURNS integer
LANGUAGE plpgsql
AS $function$DECLARE
string_in ALIAS FOR $1;
id integer;
begin
id= (select id from public.buttons where fbisn =
split_part(string_in,',',1));
return val;
end;
$function$
;
string_in is everytime the for clause of the first function is completed, the result string of that. This function will be called every time.
But currently, i will get no result.
Thanks for your help

Maybe a FOR LOOP using BY 3 is what you're struggling with
CREATE OR REPLACE FUNCTION public.uploadbutton(VARIADIC arr TEXT[])
RETURNS TEXT LANGUAGE plpgsql AS $$
DECLARE result TEXT DEFAULT '';
BEGIN
FOR i IN 1..array_length($1,1) BY 3 LOOP
result := result || '(' || array_to_string($1[i:i+2],',') || ')';
END LOOP;
RETURN result;
END;
$$;
Testing
SELECT uploadbutton(VARIADIC '{"59373033336415021231","5","a","59373033335915022fff","5","b"}'::text[]);
uploadbutton
------------------------------------------------------
(59373033336415021231,5,a)(59373033335915022fff,5,b)
(1 Zeile)
If you need the quotation marks ' in every element, just change the concatenation of the result variable accordingly:
result := result || E'(\'' || array_to_string($1[i:i+2],E'\',\'') || E'\')';
Edit: see comments
CREATE OR REPLACE FUNCTION public.uploadbutton(VARIADIC arr TEXT[])
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE slice TEXT[];
BEGIN
FOR i IN 1..array_length($1,1) BY 3 LOOP
slice := $1[i:i+2];
PERFORM edit(slice[1]::TEXT,slice[2]::INT,slice[3]::TEXT);
END LOOP;
END;
$$;

My answer may look ugly, but it works. You can optimize it if you want.
CREATE OR REPLACE FUNCTION uploadbutton(arr character varying[])
RETURNS text AS $$
DECLARE
arr_data character varying[] := arr;
array_len integer;
ar_data text;
incr INTEGER := 1;
result_data TEXT;
start_scope text := '(';
end_scope text := ')';
ins_incr integer := 1;
next_val text := '';
BEGIN
array_len := (select array_length(arr_data,1));
WHILE incr <= array_len LOOP
ar_data := CONCAT('''',arr_data[incr],'''');
IF (result_data IS NULL) THEN
result_data := CONCAT(result_data,'',ar_data);
ELSE
result_data := CONCAT(result_data,',',ar_data);
END IF;
ins_incr := ins_incr + 1;
IF (ins_incr = 4) THEN
result_data := CONCAT(start_scope,result_data,end_scope);
next_val := CONCAT(next_val,result_data);
result_data := NULL;
ins_incr := 1;
END IF;
incr := incr + 1;
END LOOP;
RETURN CONCAT(next_val,'(',result_data,')');
END $$
LANGUAGE plpgsql;
Sample:
SELECT uploadbutton(array ['59373033336415021231','5','a','59373033335915022fff','5','b','59373033335915022CCC','6','C','59373033335915022KKK','77','KK'])

Related

My function with array as parameter does not work

I proceed to specify my question and the solution I gave to the problem, for the benefit of the community.
I was trying to perform a multi-column insert using the identifier with a function.
For which, I was getting an error, my code was the following:
CREATE OR REPLACE FUNCTION acc.asignar_periodo(ids NUMERIC[], periodo INTEGER,codigo_subdiario VARCHAR)
RETURNS void
VOLATILE
AS
$$
DECLARE
cant_registros integer:= 0;
BEGIN
cant_registros := array_length(ids,1);
FOR i IN 1..cant_registros LOOP
EXECUTE'UPDATE '||$3||' SET periodo_tributario = $2 WHERE id = ids[i]';
END LOOP;
END;
$$ LANGUAGE plpgsql;
and my query is:
SELECT acc.asignar_periodo('{2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302}'::NUMERIC[],201612,'_08');
My solution was the following:
CREATE OR REPLACE FUNCTION acc.asignar_periodo(INTEGER[],INTEGER,INTEGER) RETURNS text VOLATILE AS
$$
DECLARE
qty integer:= array_length($1,1);
respuesta varchar := null;
BEGIN
FOR i IN 1..qty LOOP
EXECUTE'UPDATE _'||$3||' SET periodo_tributario = '||$2||' WHERE id = '||$1[i];
END LOOP;
respuesta := 'Periodo '||$2||' asignado a '||qty||' comprobantes del subdiario '||$3;
RETURN respuesta;
END;
$$ LANGUAGE plpgsql;
Note the correction, since when using EXECUTE it is necessary that the arguments escape the statements
There is no to loop needed to process the array. Postgres will process the entry array at once. After all set processing is what SQL is all about. Get into the mindset that whenever you write loop, likely incorrect and much slower. (Yes there occasions where it is necessary, but very few.) So: (see demo)
create or replace function asignar_periodo(ids numeric[], periodo integer,codigo_subdiario varchar)
returns void
language plpgsql
as $$
declare
stmt constant text = 'update %I set periodo_tributario = %s where id = any (''%s'')';
torun text;
begin
--torun = format(stmt, $3, $2, $1); -- this would work but
torun = format(stmt, codigo_subdiario, periodo, ids); -- I perfer parameter names to position reference
raise notice '%', torun;
execute torun;
end ;
$$;

RETURN cannot have a parameter in function with OUT parameters

I did the db migration from oracle to pgsql and got the code like below:
CREATE OR REPLACE FUNCTION PKG_UTIL_BD_LOGISTICS_getsignerinfo (
i_opCode T_MQ_LOGIC_TRACK_HEAD_LOG.LP_CODE%TYPE, i_remark T_MQ_LOGIC_TRACK_HEAD_LOG.REMARK%TYPE, i_acceptTime T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE, i_signer T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE, i_lpcode T_MQ_LOGIC_TRACK_HEAD_LOG.LP_CODE%TYPE,
o_signer OUT T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE, o_signerTime OUT T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE, o_status OUT T_MQ_LOGIC_TRACK_HEAD_LOG.STATUS%TYPE )
RETURNS RECORD AS $body$
DECLARE
v_signer T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE;
v_signerTime T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE;
v_status T_MQ_LOGIC_TRACK_HEAD_LOG.STATUS%TYPE;
BEGIN
IF i_lpcode = 'SF' THEN
IF i_opCode = '8000' THEN
IF POSITION(':back' in i_remark) > 0 THEN
v_status := '3';
ELSE
v_status := '7';
v_signerTime := i_acceptTime;
v_signer := SUBSTR(i_remark, POSITION(':' in i_remark) + 1);
END IF;
ELSIF i_opCode = '9999' THEN
v_status := '3';
ELSIF i_opCode = '80' THEN
v_status := '7';
v_signerTime := i_acceptTime;
ELSIF i_opCode = 70 THEN
v_status := i_opCode;
ELSE
v_status := '1';
END IF;
ELSE
IF i_opCode = 'signed' THEN
v_signerTime := i_acceptTime;
v_signer := i_signer;
v_status:='7';
ELSE
v_status:='1';
END IF;
END IF;
o_status := v_status;
o_signer := v_signer;
o_signerTime := v_signerTime;
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '%', 'PKG_UTIL_BD_LOGISTICS.getSignerInfo fetch parameters' || i_remark || 'value error:' || SQLERRM;
END;
$body$
LANGUAGE PLPGSQL
SECURITY DEFINER
when i executed the code,i got the error:RETURN cannot have a parameter in function with OUT parameters.Can someone help?I am new to pgsql.
The result of function with OUT parameters is specified by values of OUT parameters and only by these values. Although syntax of OUT parameters is similar between PostgreSQL and Oracle, a implementation is maximally different.
Oracle uses reference for OUT parameters - so you can write something like:
CREATE FUNCTION foo(a int, OUT b int)
RETURN boolean IS
BEGIN
b := a;
RETURN true;
END;
This function returns boolean value and as "side" effect it modifies second parameter passed by reference.
PostgreSQL doesn't support passing parameters by reference. All parameters are passed by value only. When You use OUT parameter, then there is not passed reference, but the returned values is taken from result composite. Result composite is composed only from OUT parameters. There are no space for some any other. So code:
CREATE OR REPLACE FUNCTION foo(a INT, OUT b int)
RETURNS boolean AS $$
BEGIN
b := a;
RETURN true;
END; $$ LANGUAGE plpgsql
is invalid, because real result of foo function is scalar int value, what is in contradiction with declared boolean. RETURN true is wrong too, because result is based on OUT parameters only, and then RETURN should be without any expression.
Equivalent translation of function foo from Oracle to Postgres is:
CREATE OR REPLACE FUNCTION foo(a INT, OUT b int, OUT result boolean)
RETURNS record AS $$
BEGIN
b := a;
result := true;
RETURN;
END; $$ LANGUAGE plpgsql
Easy rule - when function has OUT variables in Postgres, then RETURN statement is used only for ending execution - not for returned value specification. This values is based by OUT parameters.
Consider the following example :
CREATE OR REPLACE FUNCTION generate_taskcode(IN classIdVar character varying,IN appIdInt integer, OUT pagecoderet character varying) RETURNS character varying AS $$
DECLARE
updateCode character varying;
BEGIN
update mytable set lastcodeused = to_char(cast(lastcodeused as INTEGER)+1, 'FM999999999999999999') where
classid = classIdVar and appid= appIdInt
RETURNING concat(pageName,lastcodeused) as pageName
into updateCode;
return updateCode;
END;
$$ LANGUAGE plpgsql
notice the "updatecode" in between begin and end. If you'll try to execute this you'll get the same error as you've mentioned as you can't return variable "** return updateCode**" when you've OUT in function parameters. So the correct def would be as follows :
CREATE OR REPLACE FUNCTION generate_taskcode(IN classIdVar character varying,IN appIdInt integer, OUT pagecoderet character varying) RETURNS character varying AS $$
DECLARE
updateCode character varying;
BEGIN
update mytable set lastcodeused = to_char(cast(lastcodeused as INTEGER)+1, 'FM999999999999999999') where
classid = classIdVar and appid= appIdInt
RETURNING concat(pageName,lastcodeused) as pageName
into updateCode;
pagecoderet = updateCode;
return;
END;
$$ LANGUAGE plpgsql

Adding Values to an Array in Pascal - iIllegal Qualifier"

I am trying to create a program that prints 11 buttons so I wanted to use an array. The only change with these buttons is the name.
When I try to compile, I get the error "illegal qualifier" at my first array assignment.
type
buttonName = array[0..11] of String;
procedure PopulateButton(const buttonName);
begin
buttonName[0] := 'Sequence';
buttonName[1] := 'Repetition';
buttonName[2]:= 'Modularisation';
buttonName[3]:= 'Function';
buttonName[4]:= 'Variable';
buttonName[5]:= 'Type';
buttonName[6]:= 'Program';
buttonName[7]:= 'If and case';
buttonName[8]:= 'Procedure';
buttonName[9]:= 'Constant';
buttonName[10]:= 'Array';
buttonName[11]:= 'For, while, repeat';
end;
and in main I am trying to use this for loop
for i:=0 to High(buttonName) do
begin
DrawButton(x, y, buttonName[i]);
y:= y+70;
end;
Please know, I am very new to this and am not too confident of my knowledge in arrays, parameters/calling by constant and such.
Thank you
The parameter definition of PopulateButton() is wrong.
Try this:
type
TButtonNames = array[0..11] of String;
procedure PopulateButtons(var AButtonNames: TButtonNames);
begin
AButtonNames[0] := 'Sequence';
...
end;
...
var lButtonNames: TButtonNames;
PopulateButtons(lButtonNames);
for i := Low(lButtonNames) to High(lButtonNames) do
begin
DrawButton(x, y, lButtonNames[i]);
y:= y+70;
end;
Also pay attention to the naming conventions. Types normally begin with a T and function parameters start with an A.

Delphi - Re-split an array of string?

Let's say I have a string like this :
string1 := 'me,email1,you,email2,him,email3,them,email4';
To turn this into an array of string I simply do :
array1 := SplitString(string1,',');
This works fine.
But then, I get an array like :
array1[0] -> me
array1[1] -> email1
array1[2] -> you
array1[3] -> email2
array1[4] -> him
array1[5] -> email3
array1[6] -> them
array1[7] -> email4
I've searched a long time how to insert into SQLIte with this but there is no using
for i:= 0 to length(array1) -1
SQLExecute('INSERT INTO table(name,email) VALUES("'+array1[i]+'","'+array1[i+1]+'"');
because index 0 will be inserted as name with index 1 as email, but on the next turn, index 1 will be inserted as name with index 2 as email, when index 1 is en email, and index 2 a name... do you see the problem ?
I thought about re-spliting the first array into a second one by changing the initial string format into :
string1 := 'me-email1,you-email2,him-email3,them-email4';
to split a first time on the ' and a second time on the -, to get a 2 dimensional array, but seems this concept is over my knowledge at the moment :)
Just for the record, the Delphi RAD I'm using is quite recent, and only a few functions / tools are available at the moment.
How would you insert into sql ? Would you keep the original String format, or change it to get a 2 dimensional array ?
Iterate in pairs:
for i := 0 to length(array1) div 2 - 1 do
SQLExecute('INSERT INTO table(name,email) VALUES("'+array1[i*2]+'","'+array1[i*2+1]+'"');
You just should not use per-INSERT FOR-loop here.
It is not suited for it and it is dangerous if your string would have 3 or 7 or any other odd number of elements.
Also splicing random data right into the SQL command is extremely unreliable and fragile. http://bobby-tables.com/
You should either use a WHILE-loop with a sliding fetcher or tick-tock (finite state machine) swinging in one-per-string FOR-loop.
var q: TQuery;
// some Query-component of any library,
// including DBX, AnyDAC/ FireDAC mORMot or any other library you would use
var sa_OK, sa_err1, sa_err2: TArray<string>;
// common preparations for both methods
q.ParamCheck := True;
q.SQL.Text := 'INSERT INTO table(name,email) VALUES( :PName, :PMail )';
q.Prepared := True;
sa_OK := TArray<string>.Create( 'me','email1','you','email2','him','email3','them','email4');
// eeeaaasy example
sa_err1 := TArray<string>.Create( 'me','email1','you','email2','him');
// check this out-of-sync error too - it can happen! you should be pre-aware!
sa_err2 := TArray<string>.Create( 'Sarah O''Connor','email1','"Bobby F. Kennedy"','email2','him'#0'again','email3');
// not the letters you expected - but they can come too
Procedure Method1_Fetcher( const sa: array of string );
var i: integer;
s: string;
function Fetched: Boolean;
begin
Result := i <= High(sa);
if Result then begin
s := sa[i];
Inc(i);
end;
end;
Begin
i := Low(sa);
while true do begin
if not Fetched then break;
q.Params[1].AsWideString := s;
if not Fetched then break;
q.Params[2].AsWideString := s;
// ...you can easily add more parameters for more columns
// if not Fetched then break;
// q.Params[3].Value := s;
// ... or you can make a loop
// FOR j := 0 to q.Params.Count - 1 DO ... q.Params[j] :=...
// only executing insert if ALL the columns were filled
q.ExecSQL;
end;
q.Transaction.CommitWork;
End;
Procedure Method2_TickTock( const sa: array of string );
var i, FSM: integer;
Begin
FSM := 0;
for i := Low(sa) to High(sa) do begin
if FSM = 0 then begin
q.ParamByName('PName').AsWideString := sa[i];
FSM := 1; // next mode of tick-tock
Continue;
end;
if FSM = 1 then begin
q.ParamByName('PMail').AsWideString := sa[i];
q.ExecSQL;
// only executing insert after the last column was filled
FSM := 0; // next mode of tick-tock
Continue;
end;
/// would only come here if we made some mistake above
/// and FSM got impossible value - so no "Continue" was executed
/// show error and exit
raise EInvalidOperation.CreateFmt('FSM = %d', [FSM]);
end;
q.Transaction.CommitWork;
End;
Procedure Method3_SimplifiedFSM( const sa: array of string );
// this method is actually are streamlined method #2
// it can be made because all our steps are TOTALLY identical
// ( sans optional insert execution )
var i, FSM: integer;
Begin
FSM := 0;
for i := Low(sa) to High(sa) do begin
q.Params[ FSM ].AsWideString := sa[i];
Inc( FSM );
if FSM >= q.Params.Count then begin
q.ExecSQL; // only after (if ) last of all columns filled!
FSM := 0;
end;
end;
q.Transaction.CommitWork;
End;
Now you can debug calls like Method1(sa_OK) or Method2(sa_err1) and see how it is working and how it deals with errors

PostgreSQL - Dynamic Tables, insert values from Text array in a function

A client wants to store anything in seperate tables. (Long story, it's nescessary).
To do this, i've build an Postgres function to create new tables on the fly in it's own namespace.
These tables can have 2, 4, or 100 columns, just what the user wants.
No problem, this works. The used datatypes in these dynamic tables are native, e.g. text, booleans, integers, etcetera.
Now comes the problem, i've got to insert data into these tables. The point is, the users can not access the tables directly, they'll do this through a function.
For a couple of datatypes is this not a problem, but for the text datatypes it's problematic.
Here is the function till now:
-- Function: add(integer, text[])
-- DROP FUNCTION add(integer, text[]);
CREATE OR REPLACE FUNCTION add(id integer, fields text[])
RETURNS integer AS
$BODY$
DECLARE
l_line_ending text := ')';
l_fieldtype integer;
l_ito_table_name text;
l_ito_fieldnames text;
l_field ito_fields%rowtype;
l_first_loop boolean := true;
l_values_to_insert text := 'VALUES (';
l_loop_counter integer := 0;
l_query text;
BEGIN
select into l_ito_table_name ito_table_name from ito where id = target_ito_id;
l_ito_fieldnames := 'insert into ' || l_ito_table_name || '(';
FOR l_field IN SELECT * FROM ito_fields
WHERE ito_fields.ito_id = target_ito_id
order by ito_fields.id asc
LOOP
l_loop_counter := l_loop_counter +1;
l_fieldtype := l_field.fieldtype;
if not l_first_loop THEN
l_values_to_insert := (l_values_to_insert || ', ');
end if;
if l_field.fieldtype = 1 THEN
l_values_to_insert := (l_values_to_insert || '''' || (fields[l_loop_counter]) || '''' );
elsif l_field.fieldtype = 2 THEN
l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_integer(fields[l_loop_counter]));
elsif l_field.fieldtype = 3 THEN
l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_boolean(fields[l_loop_counter]));
elsif l_field.fieldtype = 4 THEN
l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_float(fields[l_loop_counter]));
else
return 103;
end if;
if l_first_loop then
l_ito_fieldnames := l_ito_fieldnames || l_field.column_name;
l_first_loop := false;
else
l_ito_fieldnames := l_ito_fieldnames || ', ' || l_field.column_name;
end if;
END LOOP;
l_ito_fieldnames := l_ito_fieldnames || l_line_ending;
l_values_to_insert := ((l_values_to_insert) || (l_line_ending));
l_query := (l_ito_fieldnames || l_values_to_insert);
EXECUTE l_query;
return 0;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION add(integer, text[])
OWNER TO postgres;
The table ito_fields stores all the field metadata, so the datatype, versions, descriptions are stored here.
the ito table stores all the dynamic table data.
The point in this function are the quotes. The insert function is created dynamicly, therefore i've got to add some quotes around the text fields in the insert function.
Postgres is giving errors as soon as i do that. Even with the quote_literal functions it's still a problem, because of the string concatenation (i know, security risks, but that's no problem for now).
I've tried to use quote_literal, quote_ident, even replacing the quotes (') with a replacement, until the execute function (replace(query, l_quote_rep, '''').. I really don't have a clue now how to fix this...
Thanks in advance.
Arrays, aggregates, quote_ident and quote_nullable are your friends.
This should work, and code is shorter:
CREATE OR REPLACE FUNCTION add(id integer, fields text[])
RETURNS integer AS
$BODY$
DECLARE
l_ito_table_name text;
l_query text;
l_fields text;
r_values text;
BEGIN
--get table name
SELECT INTO l_ito_table_name quote_ident(ito_table_name) FROM ito WHERE id = target_ito_id;
-- get column names
SELECT INTO l_fields
array_to_string( array_agg(quote_ident(column_name)), ',' )
FROM ito_fields
WHERE ito_fields.ito_id = target_ito_id
order by ito_fields.id asc;
-- prepare values
SELECT INTO r_values
array_to_string( array_agg(quote_nullable(u.name)), ',' )
FROM unnest(fields) u(name);
l_query := 'insert into ' || l_ito_table_name || '(' || l_fields || ') values (' || r_values || ')';
EXECUTE l_query;
return 0; -- why 0?
END;
$BODY$;

Resources