PL/pgSQL ERROR: too many parameters specified for RAISE - postgresql

I'm trying to put more than 2 parameters into a raise notice but i can't ¿How can i do it?
CREATE OR REPLACE FUNCTION TABLA_MULT(numeric) RETURNS void AS '
DECLARE
texto1 TEXT := ''multiplicado por '';
texto2 TEXT := '' es igual a '';
BEGIN
RAISE NOTICE ''TABLA DE MULTIPLICAR DEL %'',$1;
RAISE NOTICE ''=========================='';
FOR i IN 1..10 LOOP
DECLARE result numeric := ($1*i);
BEGIN
RAISE NOTICE ''El número %'',i,texto1,$1,texto2,result;
END;
END LOOP;
END; ' LANGUAGE 'plpgsql';

As documented in the manual you need one % for each parameter that should be replaced:
The number of arguments must match the number of % placeholders in the format string, or an error is raised during the compilation of the function.
So you need:
RAISE NOTICE 'El número % % % % %',i,texto1,$1,texto2,result;

Not specifically related to your question but you may want to investigate Postgres Dollar-Quoted String Constants. This relieves you from having to double quote within the function body. Thus your function (as initially posted by you becomes):
CREATE OR REPLACE FUNCTION TABLA_MULT(numeric) RETURNS void AS $$
DECLARE
texto1 TEXT := 'multiplicado por ';
texto2 TEXT := ' es igual a ';
BEGIN
RAISE NOTICE 'TABLA DE MULTIPLICAR DEL %',$1;
RAISE NOTICE '==========================';
FOR i IN 1..10 LOOP
DECLARE result numeric := ($1*i);
BEGIN
RAISE NOTICE 'El número %',i,texto1,$1,texto2,result;
END;
END LOOP;
END; $$ LANGUAGE 'plpgsql';
It would also be self beneficial to systematically indent your code.

Related

postgreSQL : oracle sqlerrm equivalent of postgres

I'm new to PostgreSQL. I have experience in oracle. In oracle , to find the exact error, I use code 'dbms_output.put_line(sqlerrm)' . Here I have a postgresql function returning an integer value
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
return -1;
end;
$BODY$;
ALTER FUNCTION public.fn_sqltest(character varying)
OWNER TO postgres;
I call this function as below
DO $$
DECLARE
ae integer;
BEGIN
ae:=fn_sqltest(10);
RAISE NOTICE 'exception: % % ', sqlstate , sqlerrm ;
RAISE NOTICE 'Return value is: % ', ae;
END $$;
and I get the error
ERROR: column "sqlstate" does not exist
How can I show the exact error message like sqlerrm in oracle.
I updated the function and wrote a code in function exception section
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
text_var1 text;
text_var2 text;
text_var3 text;
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
RAISE NOTICE 'Return value is: % % %',text_var1 , text_var2, text_var3;
return -1;
end;
$BODY$;
ALTER FUNCTION public.fn_sqltest(character varying)
OWNER TO postgres;
Another method is by changing the return type. I changed the return type to text and rewrite the exception section code as below
return sqlerrm;
Unlike PL/SQL, in PL/pgSQL SQLSTATE and SQLERRM are not defined outside an exception handler. See the documentation, section "Obtaining Information About An Error".
This also means that you can't get SQLSTATE and SQLERRM of a successful operation, unlike PL/SQL.
So, if you want to use these special variables outside an exception handler, you have to store them in variables.
I don't know how would you like to return them from the function. I can demonstrate this idea inside your function code:
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
v_sqlerrm text;
v_sqlstate text;
begin
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
v_sqlerrm := sqlerrm;
v_sqlstate := sqlstate;
end;
RAISE NOTICE 'exception: % % ', v_sqlstate , v_sqlerrm ;
return -1;
end;
$BODY$;
I know this is old but just for the record: The easiest way to output the state and message of error is to raise them from within the exception clause. You don't need to declare extra variables.
CREATE OR REPLACE FUNCTION fn_sqltest(p_id integer)
RETURNS integer
AS $$
DECLARE
n integer;
BEGIN
SELECT off_id
INTO n
FROM office
WHERE per_id = p_id;
RETURN n;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'exception: % - %', SQLSTATE, SQLERRM;
RETURN -1;
END;
$$ LANGUAGE plpgsql;

Invalid for loop in a function

I am trying to make the following function working:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER) RETURNS void AS $$
DECLARE
devices_array TEXT[] := devices;
devices_count INTEGER := array_length(devices, 1);
row_id INTEGER := campaign_id;
BEGIN
FOR device IN unnest(devices_array) LOOP
IF my_count('my_table', device, row_id) != 1;
RAISE EXCEPTION 'invalid_count %', row_id
ENDIF
END LOOP;
END
$$ LANGUAGE plpgsql;
my_count is a working function which returns INTEGER.
The definition fails with the error:
ERROR: syntax error at or near "unnest"
LINE 7: FOR device IN unnest(devices_array) LOOP
Could you spot the issue? Thanks!
I am planning to call the function as follows:
select validate_count('{foo, bar}', 1)
Use a FOREACH loop:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER)
RETURNS void
AS
$$
DECLARE
device text;
devices_count INTEGER := array_length(devices, 1);
BEGIN
FOREACH device IN ARRAY devices LOOP
IF my_count('my_table', device, campaign_id) <> 1 then
RAISE EXCEPTION 'invalid_count %', campaign_id;
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql;
A few things need to be fixed to make this valid.
The FOR loop needs SELECT before the unnest, i.e.:
FOR device IN SELECT unnest(devices_array) LOOP
You need to declare device up top, for example:
DECLARE device RECORD;
The IF statement needs a THEN instead of a ;, i.e.:
IF my_count('my_table', device, row_id) != 1 THEN
The RAISE EXCEPTION statement needs a semicolon at the end of the line, i.e.:
RAISE EXCEPTION 'invalid_count %', row_id;
The END for the IF statement should be END IF;.
The END for the LOOP statement should be END LOOP;.
Here's the net result:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER) RETURNS void AS $$
DECLARE
devices_array TEXT[] := devices;
devices_count INTEGER := array_length(devices, 1);
row_id INTEGER := campaign_id;
device RECORD;
BEGIN
FOR device IN SELECT unnest(devices_array) LOOP
IF my_count('my_table', device, row_id) != 1 THEN
RAISE EXCEPTION 'invalid_count %', row_id;
END IF;
END LOOP;
END
$$ LANGUAGE plpgsql;

Loop through a composite array in plpgsql

I want to define a composite array , initialize three elements of the array and then iterate over each of the elements in a loop. I need this loop and to iterate over it as a requirement for an issue that I have.
I have tried for several days to write the code and refer to several resources on net but this has not worked. Can someone help on it. Here is the code, even the first initialization is not working so I don't have code for rest of the requirement (to iterate after the initialization)
CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);
CREATE OR REPLACE function temp_n_bulk_load2()
returns void
as
$$
declare
v_t temp_n_inv_item[];
BEGIN
v_t[1] := ARRAY ['Item1',1,10];
v_t[2] := ARRAY ['Item2',2,20];
v_t[3] := ARRAY ['Item3',3,30];
-- raise notice 'first array % ', v_t[1];
--raise notice 'first array % ', v_t[2];
--raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
null; --added as a temporary placeholder
END;
$$
language plpgsql;
--select temp_n_bulk_load2()
Regards,
DbuserN
UPDATE
Surprisingly, after posting the question, I saw one reference and assignment is now working but I try to change the return type to the array which is not working, (which is an additional issue), though assignment is fixed now as below, but the error the below gives is "ERROR: cannot return non-composite value from function returning composite type"
Revised code
:
CREATE OR REPLACE function temp_n_bulk_load2()
returns temp_n_inv_item
as
$$
declare
v_t temp_n_inv_item[];
BEGIN
v_t[1] := row ('Item1',1,10);
v_t[2] := row ('Item2',2,20);
v_t[3] := row ('Item3',3,30);
raise notice 'first array % ', v_t[1];
raise notice 'first array % ', v_t[2];
raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
null; --added as a temporary placeholder
return v_t;
END;
$$
language plpgsql;
PLpgSQL has special statement for iteration over array. For large arrays it is much more effective (is not too significant for small arrays):
create type tp as (a int, b int);
do $$
declare a tp[];
r record;
begin
/* composite array initialization */
a = array[(1,2),(3,4),(5,6)];
/* iterate over a array */
foreach r in array a
loop
raise notice '% % %', r, r.a, r.b;
end loop;
end;
$$;
NOTICE: (1,2) 1 2
NOTICE: (3,4) 3 4
NOTICE: (5,6) 5 6
DO
Don't afraid read a documentation.
Here you are creating array of data type you generated using
CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);
So simply return array of data type as temp_n_inv_item[].
CREATE OR REPLACE FUNCTION adm.temp_n_bulk_load2()
RETURNS boolean AS
$BODY$
declare
v_t temp_n_inv_item[];
BEGIN
/*CREATE TYPE temp_n_inv_item AS (
name text,
supplier_id integer,
price numeric
);*/
v_t[1] := row ('Item1',1,10);
v_t[2] := row ('Item2',2,20);
v_t[3] := row ('Item3',3,30);
raise notice 'first array % ', v_t[1];
raise notice 'first array % ', v_t[2];
raise notice 'first array % ', v_t[3];
--iterate though each of the v_t array in a loop
//updated
FOR cnt in 1..(array_length(v_t,1))
LOOP
Raise notice ' array value % ', v_t[cnt];
END LOOP;
return true;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Hope this will solve your problem.

Postgresql dynamic cursor (for record in EXECUTE), field access

Suppose the block below:
do $$
declare
v_table_name text:='table_name';
v_column_name text:='column_name';
v_record record;
v_sql_dynamic text;
v_enum_xml xml;
v_string text;
begin
v_sql_dynamic:=format('select %s from %s',v_column_name,v_table_name);
FOR v_record IN EXECUTE v_sql_dynamic
LOOP
raise notice 'field with name % has a value of %',v_column_name,***?????***;
END LOOP;
end;
$$ language plpgsql
How do I get access to the field of v_record in the way like: v_record.v_column_name?
See Abelisto's comment. While converting the value to text anyway, to_json() is good enough and slightly faster.
DO
$$
DECLARE
v_table_name text := 'table_name';
v_column_name text := 'column_name';
v_sql_dynamic text := format('select %s from %s', v_column_name, v_table_name);
v_record record;
BEGIN
FOR v_record IN EXECUTE v_sql_dynamic
LOOP
RAISE NOTICE 'field named % has value %.'
, v_column_name, to_json(v_record) ->> v_column_name;
END LOOP;
END
$$ LANGUAGE plpgsql;
Related:
How to get the value of a dynamically generated field name in PL/pgSQL

How to execute PostgreSQL RAISE command dynamically

How to raise error from PostgreSQL SQL statement if some condition is met?
I tried code below but got error.
CREATE OR REPLACE FUNCTION "exec"(text)
RETURNS text AS
$BODY$
BEGIN
EXECUTE $1;
RETURN $1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
-- ERROR: syntax error at or near "raise"
-- LINE 1: raise 'test'
SELECT exec('raise ''test'' ') WHERE TRUE
In real application TRUE is replaced by some condition.
Update
I tried to extend answer to pass exception message parameters.
Tried code below but got syntax error.
How to pass message parameters ?
CREATE OR REPLACE FUNCTION exec(text, variadic )
RETURNS void LANGUAGE plpgsql AS
$BODY$
BEGIN
RAISE EXCEPTION $1, $2;
END;
$BODY$;
SELECT exec('Exception Param1=% Param2=%', 'param1', 2 );
You cannot call RAISE dynamically (with EXECUTE) in PL/pgSQL - that only works for SQL statements, and RAISE is a PL/pgSQL command.
Use this simple function instead:
CREATE OR REPLACE FUNCTION f_raise(text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION '%', $1;
END
$func$;
Call:
SELECT f_raise('My message is empty!');
Related:
Generate an exception with a Context
Additional answer to comment
CREATE OR REPLACE FUNCTION f_raise1(VARIADIC text[])
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION 'Reading % % %!', $1[1], $1[2], $1[3];
END
$func$;
Call:
SELECT f_raise1('the','manual','educates');
VARIADIC is not a data type, but an argument mode.
Elements have to be handled like any other array element.
To use multiple variables in a RAISE statement, put multiple % into the message text.
The above example will fail if no $3 is passed. You'd have to assemble a string from the variable number of input elements. Example:
CREATE OR REPLACE FUNCTION f_raise2(VARIADIC _arr text[])
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
_msg text := array_to_string(_arr, ' and '); -- simple string construction
BEGIN
RAISE EXCEPTION 'Reading %!', _msg;
END
$func$;
Call:
SELECT f_raise2('the','manual','educates');
I doubt you need a VARIADIC parameter for this at all. Read the manual here.
Instead, define all parameters, maybe add defaults:
CREATE OR REPLACE FUNCTION f_raise3(_param1 text = ''
, _param2 text = ''
, _param3 text = 'educates')
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION 'Reading % % %!', $1, $2, $3;
END
$func$;
Call:
SELECT f_raise3('the','manual','educates');
Or:
SELECT f_raise3(); -- defaults kick in