Triggers in Oracle PL/SQL Problems - oracle

I have just written a stored procedure and stored function that serve to insert a new row into my Orders table. The row update inserts: Ordernum, OrderDate, Customer, Rep, Manufacturer, Product, Qty, and SaleAmount.
I now have to write a trigger that updates my Salesreps table by adding the amount of the order just added. I am unsure of how to reference the rows. I have tried this:
CREATE OR REPLACE TRIGGER UpdateSalesrep
AFTER INSERT ON Orders
FOR EACH ROW
BEGIN
UPDATE Salesreps
SET Sales = Sales + :NEW.Amount
WHERE Rep = Salesrep;
End;
/
Sales is the name of the column on the Salesrep table. Amount if the name used in the stored proc. I am getting an error returned on 'Where Rep = Salesrep'. If I don't include this line, the trigger does not return any errors. However, I am assuming that if I can't figure out how to tie the sales amount into the one salesrep that made the sale, I will update every salesrep (which I'm sure they would be quite happy with). Any help would be greatly appreciated, as always.

CREATE OR REPLACE TRIGGER UpdateSalesrep
AFTER INSERT ON Orders
FOR EACH ROW
BEGIN
UPDATE Salesreps
SET Sales = Sales + :NEW.Amount
WHERE Salesrep = :NEW.Rep;
End;
/

Who have said that you have to write a trigger? Is this stated explicitly as a requirement in your homework task? You can also update table UpdateSalesRep in the same stored procedure that you have already written for inserting in table Orders.
Read: http://www.oracle.com/technetwork/issue-archive/2008/08-sep/o58asktom-101055.html

Related

i made trigger to update data when people make action in form

trigger code good but when make action this error appear
error ORA-04091: table OR_HR.SELL is mutating, trigger/function may not see it.
trigger code
create or replace TRIGGER "QUALITY_EDIT"
AFTER INSERT OR UPDATE ON Sell
FOR EACH ROW
BEGIN
UPDATE DRUG
SET QUANTITY =
(SELECT (DRUG.QUANTITY - SELL.QUANTITY ) FROM Sell
JOIN Drug
ON SELL.DRUG_ID = DRUG.DRUG_ID) ;
END;
How can i solve this problem?
You can't select from table which is just being updated (or inserted into), it is mutating and trigger can't see it.
Lucky you, you don't have to select from sell, use something like this (the :new pseudorecord) instead:
create or replace trigger quality_edit
after insert or update on sell
for each row
begin
update drug d set
d.quantity = d.quantity - :new.quantity
where d.drug_id = :new.drug_id;
end;
/

Best practice for updating column specific triggers

Welcome Oracle pro's
In an Oracle 12 database (upgrade is already scheduled ;-)) we have a setup of different tables updating a common base table via "after update" triggers like following:
Search_Flat
ID
Field_A
Field_B
Field_C
Now table1 contains n columns where let's say 2 out of n are relevant for the Search_Flat table. As the update of table1 may only affect columns not relevant for Seach_Flat we want to add checks to the trigger. So our first approach is like following:
CREATE OR REPLACE TRIGGER tr_tbl_1_au_search
AFTER UPDATE OF
field_a,
field_b
ON schemauser.search_flat
FOR EACH ROW
BEGIN
IF :new.field_a <> :old.field_a THEN
UPDATE schemauser.search_flat SET field_a = :new.field_a WHERE id = :new.ID;
END IF;
IF :new.field_b <> :old.field_b THEN
UPDATE schemauser.search_flat SET field_b = :new.field_b WHERE id = :new.ID;
END IF;
END;
Alternatively we could also setup the trigger like following:
CREATE OR REPLACE TRIGGER tr_tbl_1_au_search
AFTER UPDATE OF
field_a,
field_b
ON schemauser.search_flat
FOR EACH ROW
BEGIN
IF :new.field_a <> :old.field_a OR :new.field_b <> :old.field_b THEN
UPDATE schemauser.search_flat
SET field_a = :new.field_a,
field_b = :new.field_b
WHERE id = :new.ID;
END IF;
END;
The question now is about the setup of the triggers themselves. Which approach is the better with respect to:
locking time of search_flat rows
overall performance of affected components (i.e., table_1, trigger and search_flat)
In production we are talking about 4 tables with 10 fields each considered in the triggers. And we have independent app servers accessing the shared database updating the 4 tables simultaneously. From time to time we detect the following error which is the reason we wan't to optimize the triggers:
ORA-02049: timeout: distributed transaction waiting for lock
Sidenote: This setup has been chosen instead of a view or materialized view due to performance reasons as the base table is used in gui with the requirement to be instantly updated and the number of records of the 4 feeding tables are too high for updating materialized view on update.
I'm looking forward to the discussion and your thoughts.
As I understand your post, you have 4 live tables (called "table1", "table2", etc.) that you want to search on, but querying from them is too slow, so you want to maintain a single, flattened table to search on instead and have triggers to keep that flattened table always up-to-date.
You want to know which of two trigger approaches is better.
I think the answer is "neither", since both are prone to deadlocks. Imagine this scenario
User 1 -
UPDATE table1
SET field_a = 500
WHERE <condition effecting 200 distinct IDs>
User 2 at about the same time -
UPDATE table1
SET field_b = 700
WHERE <condition effecting 200 distinct IDs>
Triggers start processing. You cannot control the order in which the rows are updated. Maybe it goes like this:
User 1's trigger, time index 100 ->
UPDATE search_flat SET field_a = 500 WHERE id = 90;
User 2's trigger, time index 101 ->
UPDATE search_flat SET field_b = 700 WHERE id = 91;
User 1's trigger, time index 102 ->
UPDATE search_flat SET field_a = 500 WHERE id = 91; (waits on user 2's session)
User 2's trigger, time index 103 ->
UPDATE search_flat SET field_b = 700 WHERE id = 90; (deadlock error)
User 2's original update fails and rolls back.
You have multiple concurrent processes all updating the same set of rows in search_flat with no control over the processing order. That is a recipe for deadlocks.
If you wanted to do this safely, you should consider neither of the FOR EACH ROW trigger approaches you outlines. Rather, make a compound trigger to do this.
Here's some sample code to illustrate the idea. Be sure to read the comments.
-- Aside: consider setting this at the system level if on 12.2 or later
-- alter system set temp_undo_enabled=false;
CREATE GLOBAL TEMPORARY TABLE table1_updates_gtt (
id NUMBER,
field_a VARCHAR2(80),
field_b VARCHAR2(80)
) ON COMMIT DELETE ROWS;
CREATE GLOBAL TEMPORARY TABLE table2_updates_gtt (
id NUMBER,
field_a VARCHAR2(80)
) ON COMMIT DELETE ROWS;
-- .. so on for table3 and 4.
CREATE OR REPLACE TRIGGER table1_search_maint_trg
FOR INSERT OR UPDATE OR DELETE ON table1 -- with similar compound triggers for table2, 3, 4.
COMPOUND TRIGGER
AFTER EACH ROW IS
BEGIN
-- Update the table-1 specific GTT with the changes.
CASE WHEN INSERTING OR UPDATING THEN
-- Assumes ID is immutable primary key
INSERT INTO table1_updates_gtt (id, field_a) VALUES (:new.id, :new.field_a);
WHEN DELETING THEN
INSERT INTO table1_updates_gtt (id, field_a) VALUES (:old.id, null); -- or figure out what you want to do about deletes.
END CASE;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
-- Write the data from the GTT to the search_flat table.
-- NOTE: The ORDER BY in the next line is what saves us from deadlocks.
FOR r IN ( SELECT id, field_a, field_b FROM table1_updates_gtt ORDER BY id ) LOOP
-- TODO: replace with BULK processing for better performance, if DMLs can affect a lot of rows
UPDATE search_flat sf
SET sf.field_a = r.field_a,
sf.field_b = r.field_b
WHERE sf.id = r.id
AND ( sf.field_a <> r.field_a
OR (sf.field_a IS NULL AND r.field_a IS NOT NULL)
OR (sf.field_a IS NOT NULL AND r.field_a IS NULL)
OR sf.field_b <> r.field_b
OR (sf.field_b IS NULL AND r.field_b IS NOT NULL)
OR (sf.field_b IS NOT NULL AND r.field_b IS NULL)
);
END LOOP;
END AFTER STATEMENT;
END table1_search_maint_trg;
Also, as numerous commenters have pointed out, it's probably better to use a materialized view for this. If you are on 12.2 or later, real-time materialized views (aka "ENABLE ON QUERY COMPUTATION") offer a lot of promise for this sort of thing. No COMMIT overhead to your application and real-time search results. It's just that search time degrades slightly if there are a lot of recent updates to the underlying tables.

I cant find solution for the trigger mutating in PL/SQL

So i am a newbie in PL/SQL, And i want to create a trigger in which a specific record salary can not be updated or deleted while other records of the table can. Suppose the record i want not to be able to update or delete its salary is EMPNO = 7839, The trigger gets created but when i update any records in EMP table it gives me error that ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it, Can someone give me a solution for this?
This is the code:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
DECLARE
ROW_NUM NUMBER;
BEGIN
SELECT COUNT(*) INTO ROW_NUM FROM EMP WHERE EMPNO = 7839;
IF UPDATING('ROW_NUM') THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
You can convert your code into this one :
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
IF :OLD.EMPNO = 7839 AND :OLD.SAL != NVL(:NEW.SAL,0) THEN
RAISE_APPLICATION_ERROR('-20000','CAN''T UPDATE SALARY OR DELETE THE ROW FOR EMPNO = '||:OLD.EMPNO);
END IF;
END;
/
Where
No query is not needed. Just new and old versions of the concerned
SAL values should be equal for an employee in order to keep that value(7839) to
be kept within the table. For DELETING case, the :NEW values for the columns will be NULL.
Those conditions are valid for both DELETING and UPDATING, so no
need to repeat them within the code. But a column cannot be be deleted, deletion of the whole record will be the case
Repeating the trigger name at the end is optional, so might be
removed.
Demo
For starters, your query is selecting the number of records, not the record identifier - it will never return "7839", only "1" or "0" for the number of records found. Also, you can't reference the table to which the trigger belongs from within the trigger (that's your mutating table error). Lastly, 'ROW_NUM' is not a column in your table, it is a variable in your trigger, so "IF UPDATING('ROW_NUM') would always be false, assuming it compiles at all.
The most basic form of what you're looking for would be this:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
-- check to see if record being updated is restricted, then raise error
IF :OLD.EMPNO = 7839 THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
That said, one obvious flaw in this approach is that the trigger as written doesn't prevent someone from changing the employee id, so theoretically if someone changed that first then the restriction on salary change would not work. A more effective approach would be a boolean column (true/false) that would identify locked records and a check to see if that flag was set. i would also recommend using a table API package to perform the actual DML operations rather than direct SQL commands, and avoid the use of triggers altogether if possible.

How to create trigger that transfer data from one table to another tabke

How to create Trigger that transfer the sum of multiple rows to another table, i have two column qty and prise i want to transfer the sum of multiple rows to another table ex supplier
Not tested but you need to try something like this..your question is missing test data and also table information so i did understand what do you mean by adding sum of multiple rows...but this might give you an idea:
CREATE OR REPLACE TRIGGER TG_NAME
AFTER INSERT ON supplier
FOR EACH ROW
DECLARE
CURSOR cur_name IS
SELECT SUM (qty + price) sum_rows
FROM purchasing;
vRowStudent cur_name%ROWTYPE;
BEGIN
OPEN cur_name;
FETCH cur_name INTO vRowStudent;
CLOSE cur_name;
INSERT INTO supplier
(sup_column)
VALUES (vRowStudent.sum_rows);
END TG_NAME;

Oracle trigger to update a field (field+1 and field -1) when inserting and deleting

I need to make a trigger that when inserting or deleting an employee (EMPLOYERS TABLE), the attribute EMPTOTAL from table SHOPS UPDATE.
EMPLOYERS table has a field (and foreign key) called SHOP that references the SHOPS table.
I know it should be something similar to this, but I don't have any example that involves more than 1 table on my exercises.
CREATE OR REPLACE TRIGGER UPD_EMPTOTAL BEFORE INSERT OR DELETE ON EMPLOYERS FOR EACH ROW
DECLARE
BEGIN
IF INSERTING THEN
UPDATE SHOPS SET EMPTOTAL=EMPTOTAL+1;
ELSIF DELETING THEN
UPDATE SHOPS SET EMPTOTAL=EMPTOTAL-1;
END IF;
END;
(I've tried other things like UPDATE sentence or declaring a variable for the shop, but I'm not clear about that, so I just parsed here the code that I'm most sure about).
This may be all that's needed to get your trigger working:
create or replace trigger upd_emptotal
before insert or delete on employers
for each row
declare
begin
if inserting
then
-- Update only shop total for the shop
-- in the employer record.
-- :new.shop is the value being inserted in the table.
update shops
set emptotal = emptotal + 1
where shop = :new.shop;
elsif deleting
then
-- Update only shop total for the shop
-- in the employer record.
-- :old.shop is the value in the record being deleted.
update shops
set emptotal = emptotal - 1
where shop = :old.shop;
end if;
end;

Resources