Performing select operations with a cursor inside a caught exception - plsql

I'm trying to write a Stored Procedure which takes input of an ID then searches 3 different tables for that item using different criteria. One solution I have is to perform the select statement on each table one by one, and catching the NO_DATA_FOUND exception if nothing is found via the select.
In pseudocode:
Select item from from first table
If no data found, throw exception.
Handle exception by selecting data from second table
If no data found, throw another exception.
Handle exception by selecting data from third table (if the data is not present in any row it should return 0 rows)
Here's what I have:
OPEN REQUEST FOR
SELECT REQ_TYPE, REQ_TYPE_STATUS FROM TABLEONE
WHERE TABLEONE.REQ_ID = REQUESTID
AND (REQ_STATUS = 'D' OR REQ_STATUS = 'A');
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
OPEN REQUEST FOR
SELECT REQ_TYPE, '-' AS REQ_TYPE_STATUS FROM TABLETWO
WHERE TABLETWO.REQ_ID = REQUESTID;
EXCEPTION WHEN NO_DATA_FOUND THEN
begin
OPEN REQUEST FOR
SELECT REQ_TYPE, '-' as REQ_TYPE_STATUS FROM THIRDTABLE
WHERE THIRDTABLE.REQ_ID = REQUESTID;
end;
END;
If an item is found in TABLEONE, it successfully returns the data for it. However, the SELECT operations that are performed inside the caught exceptions don't seem to run as the the stored procedure doesn't return any rows.
I have separately verified that the data I'm searching for definitely exists in TABLETWO AND/OR TABLETHREE.
The syntax is valid as it compiles, it's just that it doesn't return any rows if the item doesn't exist in TABLEONE (but exists in either TABLETWO or TABLETHREE).
Any ideas?

Credit to #Franek for this.
The referenced linked article explains the following:
Cursor FOR loop is smart. It won't raise NO-DATA-FOUND as it is; if
there's nothing to be fetched, it will exit the loop and terminate
execution successfully. Therefore, if you meant to handle it as an
EXCEPTION - you can not.
What I was attempting to do is apparently not possible. Therefore, I have implemented it this way instead:
Pseudocode:
Search for the item in the first table
If item was found
select the details of it from the first table
If item was not found
search for the item in the second table
If Item was found
Select the details of it from the second table
If item was not found
Select the item from the third table (returning blank if it's not found here neither)
PL/SQL Implementation:
-- Search for the request ID in the first table
SELECT COUNT(REQ_ID) INTO requestFound FROM FIRSTTABLE
WHERE FIRSTTABLE.REQ_ID = REQUESTID
AND (REQ_STATUS = 'D' OR REQ_STATUS = 'A');
IF(REQUESTFOUND > 0) THEN
-- Select the request details
OPEN REQUEST FOR
SELECT REQ_ID, REQ_TYPE_STATUS FROM FIRSTTABLE
WHERE FIRSTTABLE.REQ_ID = REQUESTID
AND (REQ_STATUS = 'D' OR REQ_STATUS = 'A');
ELSE
-- Search for the request from the second table
SELECT COUNT(REQ_ID) INTO REQUESTFOUND FROM SECONDTABLE
WHERE SECONDTABLE.REQ_ID = REQUESTID;
IF(REQUESTFOUND > 0) THEN
-- Select the request details from second table
OPEN REQUEST FOR
SELECT REQ_TYPE, '-' AS REQ_TYPE_STATUS FROM SECONDTABLE
WHERE SECONDTABLE.REQ_ID = REQUESTID;
ELSE
-- Get the request from third table (will return as blank if nothing found)
OPEN REQUEST FOR
SELECT REQ_TYPE, '-' AS REQ_TYPE_STATUS FROM THIRDTABLE
WHERE THIRDTABLE.REQ_ID = REQUESTID;
END IF;
END IF;

Related

Trigger for checking values for inserting

I have two tables that here are involved from two different schemas.
Schema services and table task -- column ID
Schme mona_internal and table officius_unos -- column task
I need trigger when inserting in column task table officius_unos to check does exist inserting value in column id from table task. If exist, to continue inserting, it doesn't exist to raise the error.
Here is the trigger:
CREATE OR REPLACE TRIGGER mona_internal.PROBA_PROBA
BEFORE INSERT ON OFFICIUS_UNOS
FOR EACH ROW
DECLARE
task_provera number(10);
BEGIN
select id into task_provera from servis.task
where id=:new.task;
if (task_provera is null)
then raise_application_error(-20101, 'No task');
else insert into mona_internal.OFFICIUS_UNOS (task) values (:new.task);
end if;
END;
The trigger is compiled, but the problem appears when trying to insert a new value in column task table officius_unos,
it returns me this message
insert into officius_unos (task) values (291504);
Error report -
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-06512: at "MONA_INTERNAL.PROBA_PROBA", line 5
ORA-04088: error during execution of trigger 'MONA_INTERNAL.PROBA_PROBA'
ORA-06512: at "MONA_INTERNAL.PROBA_PROBA", line 10
And value 291504 exist in table task in column id.
P.S. Also try to solve this problem with check constraint, but there are forbidden subqueries. The solution that I used to overcome my problem is here
Oracle: Using subquery in a trigger
You don't need to insert in an insert trigger.
If the trigger is successful Oracle will continue with the INSERT on its own.
So the immediate solution is to remove the INSERT from the trigger:
CREATE OR REPLACE TRIGGER mona_internal.PROBA_PROBA
BEFORE INSERT ON OFFICIUS_UNOS
FOR EACH ROW
DECLARE
task_provera number(10);
BEGIN
select id
into task_provera
from servis.task
where id=:new.task;
if (task_provera is null) then
raise_application_error(-20101, 'No task');
end if;
// nothing do do here
END;
However the above still isn't correct. If the id doesn't exist in servis.tak the SELECT will throw a NO_DATA_FOUND exception.
One solution to that is to use an aggregate function that will always return one row. If no rows match the WHERE criteria, a NULL value is returned:
CREATE OR REPLACE TRIGGER mona_internal.PROBA_PROBA
BEFORE INSERT ON OFFICIUS_UNOS
FOR EACH ROW
DECLARE
task_provera number(10);
BEGIN
select max(id)
into task_provera
from servis.task
where id=:new.task;
if (task_provera is null) then
raise_application_error(-20101, 'No task');
end if;
// nothing do do here
END;
Or you could explicitely catch the exception:
CREATE OR REPLACE TRIGGER mona_internal.PROBA_PROBA
BEFORE INSERT ON OFFICIUS_UNOS
FOR EACH ROW
DECLARE
task_provera number(10);
BEGIN
select max(id)
into task_provera
from servis.task
where id=:new.task;
if (task_provera is null) then
raise_application_error(-20101, 'No task');
end if;
EXCEPTION
WHEN NOT_DATA_FOUND THEN
raise_application_error(-20101, 'No task');
END;
But the correct approach is to use a foreign key constraint for something like that.
alter table mona_internal.PROBA_PROBA
add constraint fk_proba_task
foreign key (task)
references servis.task (id);
Then you don't need a trigger at all.
This requires that the user mona_internal is not only granted the SELECT privilege on servis.task, but also the references privilege:
To do that, run the following as the SERVIS user:
grant references on task to mona_internal;

Highest record in PL/SQL

I have created a query that displays highest returned items from a table. My query it works perfectly with no errors! However, I want an efficient way of converting the query to PL/SQL block. The purpose of conversion is to handle errors.
SELECT ITEM_NO, MAX(QUANTITY) AS MAXIMUM
FROM ITEMS
WHERE CAT_NO >= (
SELECT MAX(ITEM_NUM) FROM ORDER
WHERE STATUS IN('ONE ITEM RETURNED','ALL ITEMS RETURNED')
)
GROUP BY ITEM_NO
ORDER BY ITEM_NO ASC;
Here's a way we do some exception handling in packages. I altered it to use your code as an example. Maybe you can use some ideas from it.
At the top, set a CONSTANT to the name of the procedure, and some variables to catch Oracle SQL error number and message in. Then in the body of the procedure, there is an anonymous block containing the select. First we set a variable to indicate where we are (err_loc) in case there are multiple locations where an error could be caught in one block. Then the select is issued. If an error occurs, it's caught by the EXCEPTION clause. Error info from Oracle is caught in the err* variables, the err_string is built and then emailed via the UTL_MAIL package. RAISE raises the error so the program halts. It's set up like this to be generic as possible, we can drop in the template, change the MBR_NAME, SQL, err_loc and that's it.
PROCEDURE TEST_PROC AS
MBR_NAME CONSTANT VARCHAR2(100) := 'TEST_PROC'; -- For use in error handling. Package member name.
err_nbr NUMBER; -- Holds a SQL error number if an exception occurs.
err_msg VARCHAR2(1000); -- Holds a SQL error message if an exception occurs.
err_string VARCHAR2(2000);
BEGIN
BEGIN
err_loc := 'Selecting max quantity'; -- Email subject
SELECT ITEM_NO, MAX(QUANTITY) AS MAXIMUM
FROM ITEMS
WHERE CAT_NO >= (SELECT MAX(ITEM_NUM)
FROM ORDER
WHERE STATUS IN('ONE ITEM RETURNED','ALL ITEMS RETURNED')
)
GROUP BY ITEM_NO
ORDER BY ITEM_NO ASC;
EXCEPTION
WHEN OTHERS THEN
err_nbr := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 1000);
err_string := 'ERROR: ' || err_nbr || ' occurred: ' || err_msg;
-- PKG_NAME and err_email_recip set in the body.
UTL_MAIL.send(sender => PKG_NAME||'.'||MBR_NAME||'#yourcompany.com',
recipients => err_email_recip,
subject => 'ERROR '|| err_loc,
message => CHR(13))||err_string);
RAISE;
END;
END TEST_PROC;
Have a look at cursors and records.
That way you have fetch data from the query and process the line if needed.
I don't have a database by hand to test my code, but this might give you an idea how a cursor and record work.
In order to capture a EXCEPTION you could add an exception handler and let it log the record you where busy with when the exception occured.
DECLARE
CURSOR CursorName IS
SELECT ColumnOne
FROM TableA
WHERE Name = 'Me';
RecordNumber CursorName%ROWTYPE;
BEGIN
-- Fetch the records from the cursor.
OPEN CursorName;
LOOP
FETCH CursorName INTO RecordNumber;
-- Do something with the record.
EXIT WHEN CursorName %NOTFOUND;
END LOOP;
CLOSE CursorName;
END;
/
Adding a error handeling would be done right above END:
EXCEPTION
WHEN OTHERS THEN
-- Log error message.
END;
/
Link: Error handeling
Does that answer you question a bit?

Case when no record then error (postgresql)

Struggling with a simple problem and find no solution.
https://ideone.com/Hisu8H (sqlfiddle mini) Should be two rows, but only one OK appears.
If there is a record there should be an OK result. If no records there should be ERROR result.
SELECT
CASE WHEN (SELECT count(*) from code)=0 THEN 'ERROR' else 'OK' end
FROM code
WHERE "CODE_ID"='EXISTS'
The problem is that if the CODE_ID exists, there will be a line OK but if the record is missing there will be no line fetched at all.
In a simple query this works. But not as a condition!
SELECT COALESCE(count(*), 0) FROM code
WHERE "CODE_ID"='ERROR'
Thanks in advance for any clue!
If there is no row that matches the condition then nothing is returned and you can't count it.
To return information about values that are used in a condition but do not exist in the database you typically use an outer join on the list of values:
select t.id,
code.code_id
from (
values ('error'), ('test')
) as t (id)
left join code on code.code_id = t.id;
This essentially a where code_id in ('error', 'test') query that also returns those values from the in list that do not exist in the table.
This can then be used to identify the missing values:
select t.id,
case when code.code_id is null then 'Error' else 'OK' end as status
from (
values ('error'), ('test')
) as t (id)
left join code on code.code_id = t.id;

Table is mutating, trigger/function may not see it

I create a trigger to check COUNT.
create or replace
TRIGGER TEST_TRG before INSERT OR UPDATE ON TEST
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
AVAILABLE INTEGER;
BEGIN
IF UPDATING THEN
IF(:new.STATUS = 600 OR :new.STATUS = 700) THEN
SELECT COUNT(1) INTO AVAILABLE FROM TEST T WHERE T.IDDISPLAY = :new.IDDISPLAY
AND T.STATUS NOT IN (600,700);
IF(AVAILABLE = 0) THEN
InsertOrUpdateAnotherTable(:new.IDDISPLAY, :new.STATUS, 0);
ELSE
RETURN;
END IF;
END IF;
END IF;
After I change the status to 600 in Test Table, there are errors.
ORA-04091: table USER.TEST is mutating, trigger/function may not see it
ORA-06512: at "USER.TEST_TRG ", line 8
ORA-04088: error during execution of trigger 'USER.TEST_TRG'
I will insert or update another table if the condition is met.
The error is because I try to get COUNT of the current table when the trigger is triggering the same table. This will cause mutating happens.
I have try transaction_anonymous and Compound Trigger, but at the end still same error occurs.
Anyone can help me for another solution, please.
Firstly:
This below line
IF(AVAILABLE = 0) THEN
never will be reached, even if you could eliminate mutating error.
Checking count like this: COUNT(1) INTO AVAILABLE will throw exception "no data found" when there is really no records you're quering.
Secondly:
using compund trigger with section AFTER EACH ROW let you avoid mutating as Tom wrote http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm:
Try this:
create or replace
TRIGGER TEST_TRG FOR INSERT OR UPDATE ON TEST
COMPOUND TRIGGER
AVAILABLE INTEGER;
AFTER EACH ROW IS
BEGIN
IF UPDATING THEN
IF(:new.STATUS = 600 OR :new.STATUS = 700) THEN
BEGIN
SELECT COUNT(1) INTO AVAILABLE FROM TEST T WHERE T.IDDISPLAY = :new.IDDISPLAY
AND T.STATUS NOT IN (600,700);
EXCEPTION
WHEN NO_DATA_FOUND THEN
InsertOrUpdateAnotherTable(:new.IDDISPLAY, :new.STATUS, 0);
END;
END IF;
END IF;
END AFTER EACH ROW;
END TEST_TRG;

Oracle AFTER INSERT Trigger

The following trigger will not fire. The trigger worked before adding the 'SELECT c.deposit_id … piece of code. Any help will be greatly appreciated. The trigger is meant to fire after an insert is made on CASH_OR_CREDIT table if the foreign key in this table is found to be linked to another table (TRANSACTION_TABLE).
`
CREATE OR REPLACE TRIGGER SEND_MONEY
AFTER INSERT
ON cash_or_credit
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
system_header_info NUMBER := 0;
l_dep_key NUMBER := 0;
CURSOR cur (cover_id NUMBER)
IS
SELECT header_id
FROM headers
WHERE party_site_id = cover_id;
system_header_info VARCHAR2 (10)
:= schema.necessay_functions.get_system_id ('DEPOSITS');
BEGIN
fnd_profile.put ('company_debugger', 'Y');
schema.necessay_functions.debugger ('old.deposit_id =' || :OLD.deposit_id);
schema.necessay_functions.debugger ('new.deposit_id =' || :NEW.deposit_id);
OPEN cur (system_header_info);
system_header_info := 0;
FETCH cur1 INTO system_header_info;
CLOSE cur1;
schema.necessay_functions.debugger (
'super_user.user_id =' || super_user.user_id);
schema.necessay_functions.debugger (
schema.necessay_functions.obtain_user_id (
schema.necessay_functions.get_system_id ('DEPOSITS')));
SELECT c.deposit_id
INTO l_dep_key
FROM schema.transaction_table o,
schema.linker_table r,
schema.cash_or_credit c
WHERE o.primary_key = r.primary_key
AND o.table_name = 'INDIVIDUAL_REC'
AND o.system_id = '265226'
AND o.status = 'A'
AND r.status = 'A'
AND c.foreign_key = r.primary_key
AND c.deposit_id = :NEW.deposit_id
AND r.relationship_code IN ('EMPLOYER_OF');
IF super_user.user_id =
schema.necessay_functions.obtain_user_id (
schema.necessay_functions.get_system_id ('DEPOSITS'))
AND l_dep_key = :NEW.deposit_id
THEN
schema.necessay_functions.debugger ('Inside If Condition');
FOR sys_comp
IN (SELECT *
FROM schema.transaction_table
WHERE status = 'A'
AND table_name = 'DEPOSITS'
AND primary_key = :NEW.deposit_id
AND system_id =
schema.necessay_functions.get_system_id (
'DEPOSITS'))
LOOP
schema.necessay_functions.debugger ('Inside Loop');
schema.necessay_functions.send_xml_message ('SEND_SYSTEM_MSG',
'SEND.UPDATE',
system_header_info,
sys_comp.system_id,
sys_comp.system_key);
END LOOP;
ELSE
schema.necessay_functions.send_xml_message ('SEND_SYSTEM_MSG',
'SEND.CREATE',
system_header_info,
system_header_id,
:NEW.deposit_id);
END IF;
EXCEPTION
WHEN OTHERS
THEN
schema.necessay_functions.debugger ('Sqlerrm:' || SQLERRM);
END SEND_MONEY;
/`
If it works without the SELECT c.deposit_id … piece then, presumably, that is what is causing an exception which is then being swallowed by the WHEN OTHERS exception handler being used and causing the trigger to look like it is not firing. You should be able to confirm that by checking whatever table/log schema.necessay_functions.debugger( is logging to.
What are the business rules around the l_dep_key value? Specifically, is it expected that the SELECT statement used to populate l_dep_key will always return a result (and only 1 result at that)? If so, at the very least wrap that statement with an anonymous block and explicitly handle any exceptions that conflict with those business rules.
BEGIN
SELECT c.deposit_id
INTO l_dep_key
FROM schema.transaction_table o,
schema.linker_table r,
schema.cash_or_credit c
WHERE o.primary_key = r.primary_key
AND o.table_name = 'INDIVIDUAL_REC'
AND o.system_id = '265226'
AND o.status = 'A'
AND r.status = 'A'
AND c.foreign_key = r.primary_key
AND c.deposit_id = :NEW.deposit_id
AND r.relationship_code IN ('EMPLOYER_OF');
EXCEPTION
WHEN NO_DATA_FOUND THEN
...TAKE APPROPRIATE ACTION HERE...
...POSSIBLY LOG AND RAISE...
WHEN TOO_MANY_ROWS THEN
...TAKE APPROPRIATE ACTION HERE...
...POSSIBLY LOG AND RAISE...
END;
As OldProgrammer stated in a comment, the exception handling in your provided code has much room for improvement. Should you really be swallowing any and all exceptions that may be thrown by the code in this trigger?
Also, as a general tip, when logging exceptions instead of just logging SQLERRM use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE() instead, as it gives you more context around the exception. Future you and/or future debuggers of this will thank you for it.
Thank you for all of your advice and input. I solved the problem. The exception text revealed that the table mutates when you attempt to query it leading to the trigger failure. The trick to checking the validity of the child table to the parent table after an INSERT and allowing the trigger to fire is to remove the reference to the child (trigger) table and to perform the join using :NEW.foreign_key to join to the parent table. I learned a lot while trying to debug this :)
BEGIN
SELECT COUNT(1)
INTO l_dep_key
FROM schema.transaction_table o,
schema.linker_table r
WHERE o.primary_key = r.primary_key
AND o.table_name = 'INDIVIDUAL_REC'
AND o.system_id = '265226'
AND o.status = 'A'
AND r.status = 'A'
AND o.foreign_key = r.primary_key
AND r.primary_key = :NEW.foreign_key
AND r.relationship_code IN ('EMPLOYER_OF');

Resources