My function with array as parameter does not work - arrays

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

Related

Postgresql VARADIC function, need help (beginner)

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'])

How to loop from an array received as a parameter in plpgsql function?

I'm kinda new into pgplsql and so far I have to create a function that loops an array that is received as a function.
The main idea of the function is to insert new records into a table that maps each id contained in the array received with a new formatted id, the format depends on the second parameter received and return the table "idsTable".
The problem is that when I try to create the function it sends me an error:
ERROR: loop variable of FOREACH must be a known variable or list of variables
LINE 38: FOREACH objectid IN ARRAY idsList LOOP
I'm not sure if I have to declare the objectid variable cause in the examples that I have seen they didn't.
So far I have this:
CREATE OR REPLACE FUNCTION createId(idsList varchar[], objectType varchar)
RETURNS TABLE(original_id varchar, new_id char) as
$$
BEGIN
IF LOWER(objectType) = 'global' THEN
FOREACH objectid IN ARRAY idsList LOOP
INSERT INTO idsTable(original_id, new_id)
VALUES(objectid, 'GID'||nextval('mapSquema.globalid')::TEXT);
END LOOP;
ELSE
FOREACH objectid IN ARRAY idsList LOOP
INSERT INTO idsTable(original_id, new_id)
VALUES(objectid, 'ORG'||nextval('mapSquema.globalid')::TEXT);
END LOOP;
END IF;
END;
$$ LANGUAGE plpgsql;
Any ideas of what could be wrong?
edit: I haven't add the part where the idsTable is returned.
Unrelated, but: you don't really need a loop for that. And you can simplify the function by only writing the INSERT once. You also forgot to return something from your function. As it is declared as returns table that is required:
CREATE OR REPLACE FUNCTION createid(idslist varchar[], objecttype varchar)
RETURNS TABLE(original_id varchar, new_id varchar) as
$$
declare
l_prefix text;
BEGIN
IF LOWER(objectType) = 'global' THEN
l_prefix := 'GID';
ELSE
l_prefix := 'ORG';
END IF;
RETURN QUERY --<< return the result of the insert
INSERT INTO idstable(original_id, new_id)
select t.x, l_prefix||nextval('mapSquema.globalid')::TEXT
from unnest(idslist) as t(x)
returning *
END;
$$ LANGUAGE plpgsql;

Mocking postgresql with a stored procedure

I've been going through the test files of https://github.com/DATA-DOG/go-sqlmock to figure out how to create a stored procedure for mocking purposes. I have:
_, err = db.Exec(`
CREATE OR REPLACE FUNCTION val() RETURNS INT AS
$$ SELECT 1; $$
LANGUAGE sql;
`)
if err != nil {
t.Fatal(err)
}
I get:
all expectations were already fulfilled, call to exec 'CREATE OR REPLACE FUNCTION val() RETURNS INT AS $$ SELECT 1; $$ LANGUAGE sql;' query with args [] was not expected
If, instead, I try it with
mock.ExpectExec(`
CREATE OR REPLACE FUNCTION val() RETURNS INT AS
$$ SELECT 1; $$
LANGUAGE sql;
`,
).WillReturnResult(sqlmock.NewResult(0, 0))
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
I get:
there is a remaining expectation which was not matched: ExpectedExec => expecting Exec which:
- matches sql: 'CREATE OR REPLACE FUNCTION val() RETURNS INT AS $$ SELECT 1; $$ LANGUAGE sql;'
- is without arguments
- should return Result having:
LastInsertId: 0
RowsAffected: 0
I am really confused on how to setup a basic stored procedure.
The sqlmock library works pretty well for this.
But please note that the ExpectExec receives a regular expression in order to match:
// ExpectExec expects Exec() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedExec allows to mock database response
ExpectExec(sqlRegexStr string) *ExpectedExec
You are sending that function the exact string you expect to receive without any escaping.
To escape the string, add this:
import (
"regexp"
)
And then when adding the expectation, escape your string (note the regexp.QuoteMeta):
mock.ExpectExec(regexp.QuoteMeta(`
CREATE OR REPLACE FUNCTION val() RETURNS INT AS
$$
SELECT 1;
$$
LANGUAGE sql;
`),
).WillReturnResult(sqlmock.NewResult(0, 0))
That way, the escaped regexp will match your exec command.

Passing an array to function and use it in WHERE IN clause

I want to use array(which is passed to function) under Where in clause
Here is what i tried
First created the array type
create or replace type p_emp_arr as table of number
Function is
create or replace
FUNCTION getEmployee_func ( empId_arr IN p_emp_arr)
RETURN number IS
total number(2) := 0;
BEGIN
IF(empId_arr is null)
THEN
empIdClause := '';
ELSE
empIdClause := 'AND Employee.empId in (select column_value from table('||empId_arr||'))';
END IF;
....
RETURN total;
END;
But gives error
Error(17,23): PLS-00306: wrong number or types of arguments in call to '||'
The error is because CONCAT (||) operator accepts only scalar variables (string/number), you cannot pass an array to it.
You need to execute this as a dynamic PL/SQL block.
In case you want to bind the array dynamically, try something like this.
Bind the variables using IN and OUT keywords appropriately.
In your Anonymous block string, prefix the to-be-bind variables with colon (:)
EXECUTE IMMEDIATE '
BEGIN
SELECT
COLUM1,COLUMN2..
INTO
:VAR1, :VAR2..
FROM .... WHERE...
AND Employee.empId in (select column_value from table(:empId_arr));
END;
'
USING OUT VAR1, OUT VAR2... IN empId_arr;
It can also be Simply,
OPEN EMP_CURSOR FOR
'SELECT * FROM Employee
where empId in SELECT COLUMN_VALUE FROM TABLE(:empId_arr)'
USING empId_arr ;
If you take the output as a cursor;
AS Wernfried mentioned.. Using MEMBER OF operator.
OPEN EMP_CURSOR FOR
'SELECT * FROM Employee
where empId member of :empId_arr'
USING empId_arr ;

Returning array via INOUT parameter without modifications

I'm using PostgreSQL 9.1.3 and the following functions:
CREATE OR REPLACE FUNCTION cad(INOUT args text[], OUT retval int4) AS $cad$
BEGIN
retval := 0;
RAISE NOTICE 'cad: %', args;
END;
$cad$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION dodo(in_args text[]) RETURNS text[] AS $dodo$
DECLARE
_res text[];
_rv int4;
BEGIN
_res := in_args;
EXECUTE 'SELECT cad($1)' USING _res INTO _res, _rv;
RETURN _res;
END;
$dodo$ LANGUAGE plpgsql;
When I call cad directly, I get expected output:
psql$ select cad(ARRAY['Quiz']);
NOTICE: cad: {Quiz}
-[ RECORD 1 ]---
cad | ({Quiz},0)
Time: 0,319 ms
My expected result for the dodo(ARRAY['Quiz']) call is the input array without changes. But instead I receive the following error:
psql$ select dodo(ARRAY['Quiz']);
NOTICE: cad: {Quiz}CONTEXT: SQL statement "SELECT cad($1)"
PL/pgSQL function "dodo" line 8 at EXECUTE statement
ERROR: array value must start with "{" or dimension information
CONTEXT: PL/pgSQL function "dodo" line 8 at EXECUTE statement
What is wrong here?
P.S.: I have to use EXECUTE as function to call will vary, code simplified for the purpose of question.
You want something like:
EXECUTE 'SELECT * FROM cad($1)' USING _res INTO _res, _rv;
The return type isn't two columns of text[],int it's a record of (text[],int) which needs unwrapping.

Resources