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

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;

Related

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.

Update trigger updating all rows in a table

I have a trigger I need to write to update a row to 'F' in a table called StudentGrades (which contains student_id, section_id, and grade) to fire when a student's status is changed to 'I' in a different table called student. My problem is I thought it worked, but it'll update all the values to 'F' no matter what the status is updated to and it updates the grades for all the student_id's in the table. Here's my code:
create or replace NONEDITIONABLE TRIGGER grades_trigger
AFTER UPDATE ON STUDENT
FOR EACH ROW
BEGIN
UPDATE StudentGRADES
SET GRADE = 'F'
WHERE :NEW.STATUS= 'I';
END ;
I can post more if I need to.
Thanks.
If I understood you correctly, that would be
create or replace noneditionable trigger grades_trigger
after update on student
for each row
begin
if :new.status= 'I' then --> is student's status changed to I?
update studentgrades set --> yes, it is - so - update another table ...
grade = 'F'
where student_id = :new.student_id; --> but not for ALL students - only for the one for which the trigger had fired
end if;
end;

Create trigger that updates row depending on column value from other table

I would like to create a trigger that updates a row in a table (TABLE2) depending on column value from other table (TABLE1).
Which line must be updated depends on the ID of TBL1.
CREATE OR REPLACE TRIGGER INSERT_PAGE
BEFORE UPDATE OR INSERT
ON TABLE1
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO TABLE2 (TBL1ID,STEP,PAGE) VALUES
(:NEW.TBL1ID,:NEW.STEP,15);
ELSIF UPDATING and :NEW.STATE='APPROVED' THEN
UPDATE TABLE2
SET PAGE=16 AND STEP1='TEXT123'
WHERE TBL1ID =TABLE1.TBL1ID;
END IF;
END;
Can someone help me in creating a trigger with an update statement?
Would this be as well a good way to go?
So I have two tables, the column state in table1 is changing throughtout the process.
Depending on the change of the column state from table1, I want to change or better say update the row in table 2 and set some columns like PAGE and STATE in Table 2 depending on column value STATE in Table 1.
I do not want that a new row being created each time TABLE 1 gets updated, only the corresponding row should be updated.
As far as I understood is you want to update table2 only when the state in table1 changed to 'approved' for a row and if a row is inserted in table1 trigger will insert the row in table2.
I have made some corrections to your code. Let me know if it is not what you wanted.
CREATE OR REPLACE TRIGGER INSERT_PAGE
BEFORE UPDATE OR INSERT
ON TABLE1
FOR EACH ROW
DECLARE
BEGIN
IF INSERTING THEN
INSERT INTO TABLE2 (TBL1ID,STEP,PAGE) VALUES
(:NEW.TBL1ID,:NEW.STEP,15);
ELSIF UPDATING THEN
IF :NEW.STATE = 'APPROVED' THEN
UPDATE table2 t2 SET
STATE = :NEW.STATE, PAGE=16, STEP1='TEXT123'
WHERE t2.TBL1ID = :OLD.TBL1ID;
END IF;
END IF;
END;

oracle trigger on delete

Okay so I have been stuck on this for about 2 hours now and I still couldn't find a solution.
I have 2 database instances.
Site 1 has lets say,
Table A
id
attrib1
foreignKey - (primary key of table B)
Site 2 has,
Table B
id
attrib1
I want to create a trigger on delete of a record of Table A. Which basically checks if Site 2 Table B has a reference to that particular record. If it does have that record, I want to prevent the deletion from happening. So far I have come up with this,
CREATE OR REPLACE TRIGGER CHECK_DEALERSHIP_USAGE
BEFORE DELETE on TBL_CARDEALERSHIP
FOR each ROW
declare
rowcnt number;
begin
SELECT COUNT(DEALERSHIP_ID) INTO rowcnt
from TBL_SALESPEOPLE#SITE1
where DEALERSHIP_ID = :NEW.DEALERSHIP_ID;
if (rowcnt>0) THEN
Raise_Application_Error (-20100, 'This dealership is used in the sales people table.');
end if;
end;
Then I do this,
delete from TBL_CARDEALERSHIP
where DEALERSHIP_ID='83';
But it still deletes it, even thought I have a record in the database
As DrabJay put it in the comments, the NEW record in a delete trigger is NULL, since it's after the deletion.
But, frankly, you're going at this wrong. This sort of thing should be done with foreign keys, not in a trigger.
CREATE TABLE TBL_CARDEALERSHIP (
... columns ...
CONSTRAINT fk_salesppl FOREIGN KEY (dealership_id)
REFERENCES tbl_salespeople (dealership_id)
);

Triggers in Oracle PL/SQL Problems

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

Resources