Can I make an update trigger using data from one table to update another? - oracle

I'm trying to get the suspension value in my studentstaff table to change from 'no' to 'yes' when the fine (a separate table) amount reaches >=10 for a specific person. I've also tried using IF but nothings seems to be working as I keep getting this error: ORA-04079: invalid trigger specification. "amount NUMBER(8);" is in the code as it was asking me to declare amount. I am using Oracle SQL. Thanks in advance.
CREATE TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
amount NUMBER(8);
BEGIN
CASE
WHEN Amount >= 10 THEN
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = '419746';
END CASE;
END;
/

The best option for this type of trigger is to use the when condition on trigger as follows:
CREATE OR REPLACE TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
WHEN (NEW.AMOUNY >= 10)
BEGIN
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = :new.LIBRARY_CARD_HOLDER;
END;
/
This trigger will be executed when the condition written in the WHEN clause is satisfied.

Without seeing your table definitions its hard, but here's a guess at what you might need
CREATE OR REPLACE
TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
BEGIN
if :new.Amount >= 10 THEN -- ie, the incoming AMOUNT on the FINE table
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = :new.LIBRARY_CARD_HOLDER -- ie, the STUDENT being fined
end if;
END;
/
Hopefully that makes sense and is close to what you're aiming to achieve here. In this example, I'm assuming that your FINE table, contains the amount and the library card holder (ie, the student etc)

Related

2 users running a stored procedure concurrently - what happens if DML statements in one execution affect tests/conditions in the parallel execution?

Let's say I have a PL/SQL stored procedure for creating sales orders as follows:
CREATE OR REPLACE PROCEDURE save_order
(
o_output_status OUT BINARY_INTEGER,
i_customer IN VARCHAR2,
i_product_code IN VARCHAR2,
i_quantity IN BINARY_INTEGER
)
IS
l_stock_available BINARY_INTEGER;
BEGIN
o_output_status := -1;
SELECT available
INTO l_stock_available
FROM stock
WHERE product = i_product_code
;
IF l_stock_available >= i_quantity THEN
INSERT INTO sales_orders (order_number, customer, product, quantity)
VALUES (order_seq.nextval, i_customer, i_product_code, i_quantity)
;
-- hello
UPDATE stock
SET available = available - i_quantity
WHERE product = i_product_code
;
o_output_status := 0;
END IF;
END save_order;
I think that's all pretty straightforward. But what I'd like to know is what happens when 2 users run this stored procedure concurrently. Let's say there's only 1 unit of some product left. If user1 runs the stored procedure first, attempting to create an order for 1 unit, l_stock_available gets a value of 1, the IF condition evaluates to true and the INSERT and UPDATE get executed.
Then user2 runs the stored procedure a moment later, also trying to create an order for 1 unit. Let's say the SELECT INTO for user2 gets executed by Oracle at the instant that user1's execution has reached the comment hello. At this point, user2 will also get a value of 1 for l_stock_available, the IF condition will evaluate to true, and the INSERT and UPDATE will be executed, bringing the stock level down to -1.
Do I understand correctly? Is this what will happen? How can I avoid this scenario, where 2 orders can be created for the last item in stock?
Yes, you understand correctly that the code as written has a race condition.
The simplest fix, assuming that performance requirements permit pessimistic locking, is to add a FOR UPDATE to the initial SELECT statement. That locks the particular row in the STOCKS table which would cause the second session to block until the first session's transaction either commits or rolls back. The second session would then see that the stock on hand had been decreased to 0.
If you want to implement DIY optimistic locking (using the similar mechanisms as e.g. JPA uses) add a VERSION column to your stock table with data type INT initialized with zero.
The initial selectof your procedure returns not only the available quantity but also the current VERSION:
select available, version
into l_available,l_version
from stock
where product_id = i_product_code;
If the table has the required quantity you first UPDATE it, but only if the VERSION has the same value as returned from the previous query.
update stock
set available = available - i_quantity,
version = version + 1
where product_id = 1 and
/* optimistick locking */
version = l_version;
Note that 1) the update will fail if the version does not match the value you selected.
If the update succeeds you increase the version to block others from performing an concurrent UPDATE.
The last step is to check if the update was succesful and if so you process the quantity you got.
rn := SQL%rowcount;
if rn = 1 then /* success */
insert into sales_orders (thread_id, product_id,quantity,create_ts)
values(i_thread_id, i_product_code,i_quantity,current_timestamp);
commit;
end if;
If the UPDATE changed zero rows (if failed due to changed VERSION) you should either retry of return a failure.

Update after calculate for each record ORACLE

SELECT CIF_ID,
SUM (IN_VERIFIED_DEBT + IN_FAC_WITH_OTHER + IN_FAC_WITH_BANK)
from LOS_CIF_INDV
WHERE STATUS= 'ACTIVE'
GROUP By CIF_ID;
I want to update the total column again after the user manipulates the client as update, insert but it gives an error
ORA-04098: trigger 'RLOS138.UPDATE_IN_TOTAL_COMMIT' is invalid and failed re-validation
CREATE OR REPLACE TRIGGER UPDATE_IN_TOTAL_COMMIT
AFTER UPDATE ON
LOS_CIF_INDV
FOR EACH ROW
DECLARE
inactive_id number;
BEGIN
inactive_id:=
:new.IN_VERIFIED_DEBT + :new.IN_FAC_WITH_OTHER + :new.IN_FAC_WITH_BANK;
UPDAte LOS_CIF_INDV
SET IN_TOTAL_COMMIT = inactive_id
WHERE CIF_ID = :NEW.CIF_ID;
END ;
/
I have tried this again
CREATE OR REPLACE TRIGGER RLOS138.UPDATE_IN_TOTAL_COMMIT
AFTER UPDATE ON RLOS138.LOS_CIF_INDV
FOR EACH ROW
DECLARE
inactive_id number;
BEGIN
SELECT SUM (IN_VERIFIED_DEBT+IN_FAC_WITH_OTHER+IN_FAC_WITH_BANK)
into inactive_id
from LOS_CIF_INDV
WHERE STATUS= 'ACTIVE'
and CIF_ID=:NEw.CIF_ID;
update LOS_CIF_INDV
set IN_TOTAL_COMMIT = inactive_id
where CIF_ID = :NEW.CIF_ID;
END ;
/
yes [CIF_ID] is primary key
In which case this trigger has the logic you need:
CREATE OR REPLACE TRIGGER RLOS138.UPDATE_IN_TOTAL_COMMIT
BEFORE UPDATE ON RLOS138.LOS_CIF_INDV
FOR EACH ROW
BEGIN
if :new.status = 'ACTIVE'
then
:new.IN_TOTAL_COMMIT := :new.IN_VERIFIED_DEBT + :new.IN_FAC_WITH_OTHER + :new.IN_FAC_WITH_BANK;
end if;
END ;
/
I have included the check on status because you used it in your aggregation queries, even though you omitted from the first version of the trigger. I haven't included an ELSE branch, but you may wish to add one. Also, I have assumed that the three columns in the addition are guaranteed to be not null; if that's not the case you'll need to handle that.
I have put a working demo on db<>fiddle. This includes a version of the trigger which fires on inserts as well as updates, and handles null values too....
CREATE OR REPLACE TRIGGER UPDATE_IN_TOTAL_COMMIT
-- handle INSERT as well as UPDATE
BEFORE INSERT OR UPDATE ON LOS_CIF_INDV
FOR EACH ROW
BEGIN
if :new.status = 'ACTIVE'
then
-- handle any of these columns being null
:new.IN_TOTAL_COMMIT := nvl(:new.IN_VERIFIED_DEBT,0)
+ nvl(:new.IN_FAC_WITH_OTHER,0)
+ nvl(:new.IN_FAC_WITH_BANK,0);
end if;
END ;
/
Why not after you could explain it to me
Because Oracle have written triggers that way: the AFTER EACH ROW trigger uses the finalised version of the record, the state which will be written to the database. Consequently, if we want to change any values we need to use a BEFORE EACH ROW trigger. Oracle enforces this with the error you got, ORA-04084: cannot change NEW values for this trigger type.
Just a reminder: ORA-04098 is telling you there are compilation errors in your trigger code. If you're not using an IDE which tells you what these errors are you can find them with this query:
select * from all_errors
where owner = 'RLOS138'
and name = 'UPDATE_IN_TOTAL_COMMIT' ;
(Not sure if you're connecting as RLOS138 - if you are, query USER_ERRORS instead.)
If I understood correctly, You want to update all the records having CIF_ID as an updated record with the same value in the IN_TOTAL_COMMIT column.
This is not a good idea. If you have some derived column then you should use the views instead of updating its value for every insert/update using the trigger.
If you really want to update the column then you must use the combination of Row level trigger, Statement trigger, and package variables. (Search for mutating table error in the SO)
But according to me, the best solution is to use the view, something like follows:
CREATE OR REPLACE VIEW LOS_CIF_INDV_VW AS
SELECT L.*,
COALESCE(
SUM(
CASE
WHEN STATUS = 'ACTIVE' THEN
IN_VERIFIED_DEBT + IN_FAC_WITH_OTHER + IN_FAC_WITH_BANK
END
) OVER(
PARTITION BY L.CIF_ID
),
0
) AS IN_TOTAL_COMMIT
FROM LOS_CIF_INDV L;

Insert inside trigger not firing

Hi i have this simple trigger in oracle
CREATE OR REPLACE TRIGGER OCAP_CREATE_NCRB
BEFORE INSERT
ON OCAP_TBLOCAP
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
Defect_Type varchar2(16);
out_ varchar2(60);
BEGIN
Select A.DEFECT_TYPE into Defect_Type from OCAP_TBLDEFECT A where A.DEFECT_ID = :NEW.DEFECT;
IF Defect_Type = 'C' THEN
--Create NCRB
SP_INSERTTBLD1D2(23,LPAD(:NEW.ISSUED_BY,6,'0'),0,'0','0','035823','Draft',' ',' ',34,' ',0,461,0,0,'035105',trunc(sysdate),' ','A',Lpad(:NEW.ISSUED_BY,6,'0'),Lpad(:NEW.ISSUED_BY,6,'0'),trunc(sysdate),'A',:New.BATCH_NO,out_);
--insert action
SP_INSERTTBLFORMYACTION(Lpad(:NEW.ISSUED_BY,6,'0'), out_, Lpad(:NEW.ISSUED_BY,6,'0'), Lpad(:NEW.ISSUED_BY,'0'), 'Draft');
--Insert other affected Lots
insert into TBLD2LOT(NCRBSERIESNO,LOTNO,CREATEDBY,CREATEDDT,SEQNO) Select (out_), A.BATCH_NO,Lpad(:NEW.ISSUED_BY,6,'0'),sysdate,(TBLD2LOTSEQ.nextval) from OCAP_OTHERBATCH A where A.OCAP_ID = :NEW.OCAP_NO;
--add NCRBSeries no. to table OCAP_TBLOCAP for referencing
Update OCAP_TBLOCAP set NCRBSERIESNO = out_ where OCAP_NO = :NEW.OCAP_NO;
--Insert ocap history
END IF;
END Ocap_Create_NCRB;
/
the first 2 stored procedure is working fine but the insert query is not .
I try to excute the insert query manunaly by replacing the Out_ and the :new.Ocap_no it is working fine.
Is there something wrong in my query?
Hope someone help me out with this.
If it isn't working, then
from OCAP_OTHERBATCH A
where A.OCAP_ID = :NEW.OCAP_NO; --> this condition is never met
which means that no rows in OCAP_OTHERBATCH contain OCAP_ID value which is equal to :NEW.OCAP_NO.
Might be because of wrong letter case, CHAR datatype (right-padded with spaces up to column's full length), ... who knows. Without tables' description and sample data, it is difficult to guess.

Oracle Trigger Conditional Rows

I'am just exploring Trigger in Oracle.
I have table like this
Note : WT_ID Id is FK of Water Summary but not have constraint(not directly connected)
I want to make trigger in Temp_tank table, if there are update in Table Temp_tank, it will sum all temp_tank volume with same WT_ID then updated it to Water_summary.Water_Use. Because of bussiness requirement, not all water_summary data will update. in this example only Home A will be affected
This is MyCode
CREATE OR REPLACE TRIGGER UPD_WaterUse
AFTER UPDATE ON Temp_tank
DECLARE
temp_wat number;
homeA_id= 1;
BEGIN
IF (WT_ID = homeA_id) THEN
SELECT SUM(ss.Volume) INTO temp_wat
from Temp_tank ss WHERE ss.Daytime = DAYTIME and ss.WT_ID =homeA_id;
-- functionUpdate(homeA_id,Daytime,temp_wat) ;
ELSE
NULL;
END IF;
END;
/
The question is, in line
IF (WT_ID = homeA_id) THEN
when i compiled, the line is ignored because WT_ID is not identifier.
is trigger cannot accept this style of code?

PL/SQL Update Trigger Updates All Rows

New to working with PL/SQL and trying to create a statement level trigger that will change the 'Reorder' value to 'Yes' when the product quantity (p_qoh) is either less than 10 or less than two times the product minimum (p_min). And if that's not the case, then to change the 'Reorder' value to 'No'. My problem is that when I perform an update for a specific product, it changes the reorder value of all rows instead of the one I'm specifying. Can't seem to figure out where I'm going wrong, think I've been staring at it too long, any help is greatly appreciated.
CREATE OR REPLACE TRIGGER TRG_AlterProd
AFTER INSERT OR UPDATE OF p_qoh, p_min ON product
DECLARE
v_p_min product.p_min%type;
v_p_qoh product.p_qoh%type;
CURSOR v_cursor IS SELECT p_min, p_qoh FROM product;
BEGIN
OPEN v_cursor;
LOOP
FETCH v_cursor INTO v_p_min, v_p_qoh;
EXIT WHEN v_cursor%NOTFOUND;
IF v_p_qoh < (v_p_min * 2) OR v_p_qoh < 10 THEN
UPDATE product SET p_reorder = 'Yes';
ELSE
UPDATE product SET p_reorder = 'No';
END IF;
END LOOP;
END;
/
The update command :
UPDATE product SET p_reorder = 'Yes';
updates all of your rows because you are not specifying a WHERE clause.
What you can do is to retrieve the product's id (product_id) using your cursor and save it so that you would use it this way:
UPDATE product SET p_reorder = 'Yes' WHERE id = product_id;
Whoaa, this is not how you do triggers.
1 - Read the Oracle Trigger Documentation
2 - (almost) Never do a commit in a trigger. That is the domain of the calling application.
3 - There is no need to select anything related to product. You already have the product record at hand with the :new and :old pseudo records. Just update the column value in :new as needed. Example below (not checked for syntax errors, etc.);
CREATE OR REPLACE TRIGGER TRG_AlterProd
BEFORE INSERT OR UPDATE OF p_qoh, p_min ON product
FOR EACH ROW
BEGIN
IF :new.p_qoh < (:new.p_min * 2) OR :new.p_qoh < 10 THEN
:new.p_reorder = 'Yes';
ELSE
:new p_reorder = 'No';
END IF;
END;
#StevieP, If you need to commit inside a trigger, you may want to consider doing it as Autonomous Transaction.
Also, sorry if my understanding of your problem statement is wrong, but your it sounded to me like a row level trigger - are you only updating the current row or are you scanning the entire table to change status on several rows? If it's on current row, #OldProgrammer's solution seems right.
And I am just curious, if you do an UPDATE statement inside the trigger on the same table, wouldn't it generate (recursive) trigger(s)? I haven't done statement triggers like this, so sorry if this is not the expected trigger behavior.
To me a statement trigger would make more sense, if the trigger was on say, sales table, when a product is sold (inserted into sales table), it will trigger the corresponding product id records to be updated (to REORDER) in Product table. That will prevent recursion danger also.

Resources