Oracle AFTER INSERT Trigger - plsql

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

Related

After Insert Trigger sintax error - ORA-00922

There is a table which i want to update each row after inserted, when the user_type of my schedule table is a "superuser". I tried to convert the table/column names to "simplify", so some names may not make sense, it's ok.
The trigger code:
CREATE OR REPLACE TRIGGER "TR_UPT_SCHEDULE"
AFTER INSERT
ON SCHEDULE
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE V_UserCode VARCHAR2(20);
BEGIN
--Find "super user" code.
SELECT UserTypeCode INTO V_UserCode FROM
(SELECT UL.User_Code,
UFT.UserTypeCode,
ROW_NUMBER() OVER (PARTITION BY UL.User_Code ORDER BY UF.UserTypeCode DESC) RN
FROM UserLogin UL
JOIN UserFunction UF
ON UL.User_Code = UF.User_Code
JOIN UserFuncType UFT
ON UFT.UserTypeCode = UF.UserTypeCode
WHERE UFT.FuncType = 'S'
) WHERE RN = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_UserCode := NULL;
IF V_UserCode IS NOT NULL
THEN UPDATE SCHEDULE
SET :NEW.UserTypeCode = V_UserCode,
OrigScheType = :NEW.UserTypeCode
WHERE CompCode = :NEW.CompCode
AND UserTypeCode = :NEW.UserTypeCode
AND ScheOrigin = :NEW.ScheOrigin
AND ScheCode = :NEW.ScheCode;
END IF;
END;
When i try to create the trigger, is returned the fallowing error:
ORA-00922
I'm pretty sure the problem is the UPDATE part, where tried compare in the WHERE clause, the Schedule table keys to be sure of which row i'm updating(inserted), but i could not realize the problem.
In triggers, you don't update tablename set :new.columnname = some value, you directly assign values to :new.columnname using PL/SQL assignments or select into. Therefore, from a syntax point of view, the final update should be replaced with something like:
:new.usertypecode := v_usercode;
:new.origschetype := v_usercode;
(I am assuming the update is only intended to apply to the current row. If the idea is to update multiple rows in the triggering table, you can't do that in a row-level trigger.)
However, this logic doesn't look like it will work:
exception
when no_data_found then
v_usercode := null;
if v_usercode is not null then
v_usercode has to be null at this point, so you don't have a value to set :new.usertypecode to, and I'm not sure what you want the trigger to do.

Plsql case is not reading properly from case

I need to read data row by row and update it based on the matching case.
My code is just reading ORBREFTND and inserting Refund from case , not reading other cases. What am i doing wrong.
DECLARE
v_tot_rows NUMBER (3);
v_eval ISG.SWR_CERT_TXN_MSG_TYPE.DESC_TX%TYPE;
CURSOR pltfm_msg_type_cur
IS
SELECT sctmt.PLTFM_MSG_TYPE_CD
FROM ISG.SWR_CERT_TXN_MSG_TYPE sctmt
JOIN ISG.FEATURE_VALUES ft_val
ON sctmt.SWR_CERT_FETR_VAL_ID = ft_val.FEATURE_VAL
JOIN ISG.FEATURE_CATEGORY ft_cat
ON ft_val.FEATURE_CAT = ft_cat.FEATURE_CAT
WHERE ft_cat.SHORT_DESCRIP = 'PLT'
AND ft_val.FEATURE_VALUE = 'Orbital';
BEGIN
FOR msg_code IN pltfm_msg_type_cur
LOOP
CASE msg_code.PLTFM_MSG_TYPE_CD
WHEN 'WAC'
THEN
v_eval := 'Authorization and Mark for Capture';
WHEN 'ORBAUTHCAP'
THEN
v_eval := 'Authorization Capture';
WHEN 'ORBREFTND'
THEN
v_eval := 'Refund';
END CASE;
UPDATE ISG.SWR_CERT_TXN_MSG_TYPE sctmt
SET sctmt.DESC_TX = v_eval;
END LOOP;
COMMIT;
v_tot_rows := SQL%ROWCOUNT;
/* Implicit Attribute %ROWCOUNT is used to find the number of rows affected by the update command */
DBMS_OUTPUT.PUT_LINE ('Total records updated : ' || v_tot_rows);
END;
You're missing a where-condition:
UPDATE ISG.SWR_CERT_TXN_MSG_TYPE sctmt
SET sctmt.DESC_TX = v_eval
WHERE sctmt.primarykey = msg_code.primarykey; -- This one is missing. Don't know your primary key..
Without the where you'll always set the value of the last cursor-object to all objects in your table ISG.SWR_CERT_TXN_MSG_TYPE.
You also need to select the key from your table:
CURSOR pltfm_msg_type_cur
IS
SELECT sctmt.PLTFM_MSG_TYPE_CD,
sctmt.PRIMARYKEY -- select key here
FROM ISG.SWR_CERT_TXN_MSG_TYPE sctmt
JOIN ISG.FEATURE_VALUES ft_val
ON sctmt.SWR_CERT_FETR_VAL_ID = ft_val.FEATURE_VAL
JOIN ISG.FEATURE_CATEGORY ft_cat
ON ft_val.FEATURE_CAT = ft_cat.FEATURE_CAT
WHERE ft_cat.SHORT_DESCRIP = 'PLT'
AND ft_val.FEATURE_VALUE = 'Orbital';

How to use current cursor value in Select query with in procedure

I am trying to use c_msisdn value in cursor for select query but it gives error of No data found, However if I supply value myself like WHERE MSISDN = '315XXX' instead of WHERE MSISDN = c_msisdn result is returned. How can I use cursor value inside select.
DECLARE
c_msisdn SSM_SMSCDATA.MSISDN%type;
c_status SSM_SMSCDATA.STATUS%type;
c_promo_id SSM_SMSCDATA.PROMO_ID%type;
view_all_status char(50) := '';
unsub_all_status char(50) := '';
services_subscribed char(400) := '';
CURSOR c_smscdata is SELECT MSISDN, STATUS, PROMO_ID FROM SSM_SMSCDATA;
BEGIN
OPEN c_smscdata;
LOOP
FETCH c_smscdata into c_msisdn, c_status, c_promo_id;
SELECT SUBSCRIPTION_ID into services_subscribed FROM (SELECT LISTAGG(SUBSCRIPTION_ID, '-') WITHIN GROUP (ORDER BY MSISDN) AS SUBSCRIPTION_ID
FROM SINGLESUBSCRIPTION
WHERE MSISDN = c_msisdn
GROUP BY MSISDN);
IF c_promo_id = '2' THEN
view_all_status := 'View All';
ELSE view_all_status := 'Unsub All';
END IF;
IF c_status = 3 THEN
unsub_all_status := 'View Successful';
ELSE unsub_all_status := 'Unsuccessful';
END IF;
INSERT INTO SSM_DAILY_REPORT (msisdn,view_all,unsub_all,SERVICES)
VALUES (c_msisdn,view_all_status,unsub_all_status,services_subscribed);
--dbms_output.put_line(c_msisdn || ' ' || c_status || ' ' || c_promo_id);
EXIT
WHEN c_smscdata%notfound;
END LOOP;
CLOSE c_smscdata;
END;
The most likely reason why you're getting the NO_DATA_FOUND exception is because you have msisdn values in your ssm_smscdata table that aren't present in your singlesubscription table.
If I were you, I wouldn't bother using a cursor for loop. Instead, I'd do it all in a single INSERT statement, like so:
INSERT INTO ssm_daily_report (msisdn, view_all, unsub_all, services)
SELECT scs.msisdn,
CASE WHEN scs.promo_id = '2' THEN 'View All';
ELSE 'Unsub All'
END view_all_status,
CASE WHEN scs.status = 3 THEN 'View Successful';
ELSE 'Unsuccessful'
END unsub_all_status,
sss.services_subscribed
FROM ssmsmscdata scs
INNER JOIN (SELECT msisdn,
LISTAGG(subscription_id, '-') WITHIN GROUP (ORDER BY msisdn) AS services_subscribed
FROM singlesubscription
GROUP BY msisdn) sss ON sss.msisdn = scs.msisdn;
That way, you avoid reinventing the nested loop join (freeing up Oracle to choose the join type it thinks is best to use, which may or may not be nested loops). You also avoid all the context switching and row-by-row processing that your cursor loop involves, and you get Oracle to do all the work in one fell swoop.
Also, by doing the inner join, you avoid the thorny issue of rows that appear in the ssm_smscdata table but not the singlesubscription, as those rows won't be returned. Should you need those rows to be returned as well, you would need to convert the INNER JOIN in the query above into an OUTER JOIN.

open, fetch, into multiple variables

I am trying to get 2 variables out of a cursor without using a loop.
CREATE OR REPLACE PROCEDURE NAK.SET_ORDERS(P_ORDER_ID NAK.ORDER_ID%TYPE)
CURSOR C_GET_ORDER_NO IS
SELECT O.ORDER_ID, O.ORDER_MAL FROM NAK.ORDERS O WHERE O.ORDER_ID = P_ORDER_ID;
BEGIN
V_ORDER_SEQ := NULL;
V_ORDER_MAL := NULL;
OPEN C_GET_ORDER_NO;
FETCH C_GET_ORDER_NO VALUES(O.ORDER_ID, O.ORDER_MAL)
INTO (V_ORDER_ID, V_ORDER_MAL);
CLOSE C_GET_ORDER_NO;
END;
Do you really need an explicit cursor? You can simply do this:
CREATE OR REPLACE PROCEDURE NAK.SET_ORDERS(P_ORDER_ID IN NAK.ORDER_ID%TYPE)
V_ORDER_SEQ := NULL;
V_ORDER_MAL := NULL;
BEGIN
SELECT O.ORDER_ID,
O.ORDER_MAL
INTO V_ORDER_SEQ,
V_ORDER_MAL
FROM NAK.ORDERS O
WHERE O.ORDER_ID = P_ORDER_ID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line("No record found");
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line("More than one record found");
WHEN OTHER THEN
dbms_output.put_line("Other problem happend");
END;
Important: this procedure will return a exception if the query doesn't return exactly one record. (ORA-01403: no data found or ORA-00913: too many values)
Alternatively you should be able to make something like:
CREATE OR REPLACE PROCEDURE NAK.SET_ORDERS(P_ORDER_ID NAK.ORDER_ID%TYPE)
CURSOR C_GET_ORDER_NO IS
SELECT O.ORDER_ID,
O.ORDER_MAL
FROM NAK.ORDERS O
WHERE O.ORDER_ID = P_ORDER_ID;
BEGIN
V_ORDER_SEQ := NULL;
V_ORDER_MAL := NULL;
OPEN C_GET_ORDER_NO;
FETCH C_GET_ORDER_NO INTO V_ORDER_ID, V_ORDER_MAL;
CLOSE C_GET_ORDER_NO;
END;

Check a record IS NOT NULL in plsql

I have a function which would return a record with type my_table%ROWTYPE, and in the caller, I could check if the returned record is null, but PL/SQL complains the if-statement that
PLS-00306: wrong number or types of arguments in call to 'IS NOT NULL'
Here is my code:
v_record my_table%ROWTYPE;
v_row_id my_table.row_id%TYPE := 123456;
begin
v_record := myfunction(v_row_id)
if (v_record is not null) then
-- do something
end if;
end;
function myfunction(p_row_id in my_table.row_id%TYPE) return my_table%ROWTYPE is
v_record_out my_table%ROWTYPE := null;
begin
select * into v_record_out from my_table
where row_id = p_row_id;
return v_record_out;
end myfunction;
Thanks.
As far as I know, it's not possible. Checking the PRIMARY KEY or a NOT NULL column should be sufficient though.
You can check for v_record.row_id IS NULL.
Your function would throw a NO_DATA_FOUND exception though, when no record is found.
You can't test for the non-existence of this variable so there are two ways to go about it. Check for the existence of a single element. I don't like this as it means if anything changes your code no longer works. Instead why not just raise an exception when there's no data there:
I realise that the others in the exception is highly naughty but it'll only really catch my table disappearing when it shouldn't and nothing else.
v_record my_table%ROWTYPE;
v_row_id my_table.row_id%TYPE := 123456;
begin
v_record := myfunction(v_row_id)
exception when others then
-- do something
end;
function myfunction(p_row_id in my_table.row_id%TYPE) return my_table%ROWTYPE is
v_record_out my_table%ROWTYPE := null;
cursor c_record_out(c_row_id char) is
select *
from my_table
where row_id = p_row_id;
begin
open c_record_out(p_row_id);
fetch c_record_out into v_record_out;
if c_record_out%NOTFOUND then
raise_application_error(-20001,'no data);
end if;
close c_record_out;
return v_record_out;
end myfunction;

Resources