When exception raised rollingback the insert - postgresql

I am calling the function
SELECT sbc_schemaA.func1(customernum => '5566789',custlocID => 25)
Whenever there is an exception raised in sbc_schemaD.func5 the insert statemet of sbc_schemaC.func4 is rolledback.
Even exception is raised it should not rollback the into custgartbl.
Please help me.
The code of the function sbc_schemaA.func1 as below.
It is calling another function "sbc_schemaB.func2" inside it.
create or replace function sbc_schemaA.func1(
customernum in VARCHAR default null,
custlocID in NUMERIC default null
) RETURNS void AS
$$
declare
ajsid numeric;
begin
select * from sbc_schemaB.func2(customernum, custlocID) into strict ajsid;
exception
when raise_exception then
RAISE info '%','ErrMsg: ' || substr(sqlerrm,1,instr(sqlerrm,e'\n',1,1)) ;
end $$ language plpgsql;
The code of the function sbc_schemaB.func2 as below.
It is calling another function "sbc_schemaC.func3" inside it.
create or replace function sbc_schemaB.func2(
customernumber IN VARCHAR,
custlocID IN NUMERIC,
brsid out NUMERIC) AS
$$
declare
v_ermsg text;
v_cnx text;
begin
select * from sbc_schemaC.func3(customernumber => customernumber,custlocID => custlocID) into strict brsid;
exception
when raise_exception then
GET STACKED DIAGNOSTICS v_ermsg = MESSAGE_TEXT,
v_cnx = PG_EXCEPTION_CONTEXT;
v_ermsg := set_context(v_ermsg::text, v_cnx::Text);
raise exception e'%', v_ermsg;
end $$ language plpgsql;
The code of the function sbc_schemaC.func3 as below.
It is calling another function "sbc_schemaC.func4" and "sbc_schemaD.func5" inside it.
create or replace function sbc_schemaC.func3(customernumber IN VARCHAR,
custlocID IN NUMERIC,
ajsid out NUMERIC,
brsid out NUMERIC) AS
$$
declare
v_ajsid numeric;
btpID numeric:=0;
select * from sbc_schemaC.func4(p_customernum => customernumber,
p_custlocID => custlocID
) into v_ajsid;
ajsid := v_ajsid;
if v_ajsid = 0 then
begin
select * from sbc_schemaD.func5(customernumber,btpID);
exception
when raise_exception then
GET STACKED DIAGNOSTICS v_ermsg = MESSAGE_TEXT,
v_cnx = PG_EXCEPTION_CONTEXT;
v_ermsg := set_context(v_ermsg::text, v_cnx::Text);
raise exception e'%', v_ermsg;
end;
end if;
end $$ language plpgsql;
The code of the function sbc_schemaC.func4 as below.
CREATE OR REPLACE FUNCTION sbc_schemaC.func4(
p_customernum VARCHAR,
p_custlocID numeric,
p_ajsid OUT numeric
) AS
$$
declare
v_cnx text;
v_ermsg text;
begin
insert into custgartbl(account_num,custlocID) values(p_customernum,p_custlocID);
exception
when raise_exception then
GET STACKED DIAGNOSTICS v_ermsg = MESSAGE_TEXT,
v_cnx = PG_EXCEPTION_CONTEXT;
v_ermsg := set_context(v_ermsg::text, v_cnx::Text);
raise exception e'%', v_ermsg;
end $$ language plpgsql;
The code of the function sbc_schemaD.func5 as below.
CREATE OR REPLACE FUNCTION sbc_schemaD.func5 (customernumber text, btpID numeric) AS $body$
DECLARE
errmsg text;
v_ermsg text;
v_cnx text;
BEGIN
if btpID = 0 then
errmsg := cermssg('error')
raise exception e'%',errmsg;
end if;
exception
when raise_exception then
GET STACKED DIAGNOSTICS
v_ermsg = MESSAGE_TEXT,
v_cnx = PG_EXCEPTION_CONTEXT;
v_ermsg := v_ermsg||E'\n'||v_cnx;
raise exception e'%', v_ermsg;
END;
$body$
LANGUAGE PLPGSQL
Thanks in advance.

The code you post for func3 is buggy; it is missing the initial BEGIN.
Anyway, when simplified, your code looks like this:
SELECT * FROM func4(...) INTO ...;
IF v_ajsid = 0 THEN
BEGIN
SELECT * FROM func5(...);
EXCEPTION
WHEN raise_exception THEN
RAISE EXCEPTION ...;
END;
END IF;
Since you raise an exception in the exception handler, the whole (sub?)transaction in which func3 is executed gets rolled back, so the effects of the call to func4 are undone.
You have to something other than raising an exception in the exception handler if you want to preserve the effects of the call to func4.

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;

Having RETURN issue in PLPGSQL

I have created this store procedure to return a row_version if the organization id is in the database.
CREATE OR REPLACE FUNCTION sote.validate_row_version(a BIGINT, b BIGINT)
RETURNS text AS $$
DECLARE
ret RECORD;
v_error_stack text;
BEGIN
SELECT row_version INTO ret
FROM sote.organizations
WHERE organization_id = a;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'Organization Id (%) was not found', myOrganizationID;
GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
RETURN to_json(v_error_stack);
RETURN '0';
END;$$ LANGUAGE plpgsql;
When I run this command,
SELECT sote.validate_row_version(2,4);
I get the following error. I don't understand why. Any help would be great.
ERROR: control reached end of function without RETURN
There are 2 problems:
your select statement will not raise an exception unless you specify SELECT ... INTO STRICT
your RETURN '0' statement is part of the exception handler and will never be executed.
So you can rewrite your function as
CREATE OR REPLACE FUNCTION sote.validate_row_version(a BIGINT, b BIGINT)
RETURNS text AS $$
DECLARE
ret RECORD;
v_error_stack text;
BEGIN
SELECT row_version INTO STRICT ret
FROM sote.organizations
WHERE organization_id = a;
RETURN '0';
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'Organization Id (%) was not found', myOrganizationID;
GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
RETURN to_json(v_error_stack);
END;$$ LANGUAGE plpgsql;
You need to move the RETURN '0' so it's executed before the EXCEPTION handler:
CREATE OR REPLACE FUNCTION sote.validate_row_version(a BIGINT, b BIGINT)
RETURNS text AS $$
DECLARE
ret RECORD;
v_error_stack text;
BEGIN
SELECT row_version INTO ret
FROM sote.organizations
WHERE organization_id = a;
RETURN '0';
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'Organization Id (%) was not found', myOrganizationID;
GET STACKED DIAGNOSTICS v_error_stack = PG_EXCEPTION_CONTEXT;
RETURN to_json(v_error_stack);
END;$$ LANGUAGE plpgsql;
The code below EXCEPTION is all part of the exception block and won't be executed unless an exception occurs.
Best of luck.

Handling EXCEPTION and return result from function

This is my code
CREATE OR REPLACE FUNCTION test_excep (arg INTEGER) RETURNS INTEGER
AS $$
DECLARE res INTEGER;
BEGIN
res := 100 / arg;
BEGIN
EXCEPTION
WHEN division_by_zero
THEN RETURN 999;
END;
RETURN res;
END;
$$
LANGUAGE plpgsql;
That is, I need returned "999", if happened division by zero, but this: SELECT test_excep(0)
returns error: division by zero
CONTEXT: PL/pgSQL function test_excep(integer) line 4 at assignment
What is wrong in my code?
The EXCEPTION clause needs to be in the same block as the exception.
For instance:
CREATE OR REPLACE FUNCTION test_excep (arg integer)
RETURNS integer
AS
$func$
DECLARE
res INTEGER;
BEGIN
res := 100 / arg;
RETURN res;
EXCEPTION
WHEN division_by_zero
THEN RETURN 999;
END
$func$
LANGUAGE plpgsql;
Here is a correct function:
CREATE OR REPLACE FUNCTION test_excep (arg INTEGER) RETURNS INTEGER
AS $$
DECLARE res INTEGER;
BEGIN
res := 100 / arg;
RETURN res;
EXCEPTION
WHEN division_by_zero
THEN RETURN 999;
END;
$$
LANGUAGE plpgsql;
EXCEPTION part must be inside the block where the exception is, and also must be the last part of the block.

the cursor for loop in postgresql

We have a function written in pl/sql(oracle) as below:
CREATE OR REPLACE PROCEDURE folder_cycle_check (folder_key IN NUMBER, new_parent_folder_key IN NUMBER) IS
parent_of_parent NUMBER;
ILLEGAL_CYCLE EXCEPTION;
CURSOR parent_c IS
SELECT parent_folder_key FROM folder
WHERE folder_key = new_parent_folder_key;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE ILLEGAL_CYCLE;
END IF;
FOR parent_rec IN parent_c LOOP
BEGIN folder_cycle_check(folder_key, parent_rec.parent_folder_key); END;
END LOOP;
END;
Now, i have to rewrite this same procedure in pl/pgsql(PostgreSQL) to achieve similar functionality. Please help me and send that pl/pgsql function.
Edit (formatted code from the comments)
CREATE OR REPLACE FUNCTION folder_cycle_check(IN folder_key INTEGER, IN new_parent_folder_key INTEGER)
RETURNS VOID
AS $procedure$
DECLARE parent_of_parent INTEGER;
PARENT_C CURSOR FOR
SELECT parent_folder_key
FROM folder
WHERE folder_key = new_parent_folder_key;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE EXCEPTION 'ILLEGAL_CYCLE';
END IF
FOR parent_rec IN (SELECT parent_folder_key FROM folder WHERE folder_key = new_parent_folder_key) LOOP
PERFORM folder_cycle_check(folder_key,parent_rec.parent_folder_key);
END LOOP;
RETURN;
END;
$procedure$
LANGUAGE plpgsql;
This should work:
CREATE OR REPLACE FUNCTION folder_cycle_check (p_folder_key INT4, p_new_parent_folder_key INT4) RETURNS VOID AS $$
DECLARE
v_parent_rec RECORD;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE EXCEPTION 'ILLEGAL_CYCLE';
END IF;
FOR v_parent_rec IN SELECT parent_folder_key FROM folder WHERE folder_key = p_new_parent_folder_key LOOP
PERFORM folder_cycle_check(folder_key, v_parent_rec.parent_folder_key)
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;