How to use Trigger with joining 2 tables? - oracle

I have 2 tables one is Schedule_table :
SID NOT NULL NUMBER(38)
FNUMBER VARCHAR2(20)
DEPARTURE_TIME TIMESTAMP(6) WITH TIME ZONE
ARRIVAL_TIME TIMESTAMP(6) WITH TIME ZONE
PRICE NUMBER
The second table is Flight_table
FNUMBER NOT NULL VARCHAR2(20)
DEPARTURE_APCODE CHAR(3)
ARRIVAL_APCODE CHAR(3)
Fnumber is PK in Flight_table and FK in Schedule_table.
I want the trigger to fire when the PRICE gets changed in Schedule_table
Also, to print a message of (Fnumber, ARRIVAL_APCODE, DEPARTURE_TIME ,ARRIVAL_APCODE).
I wrote this Code, it did not work.
create or replace trigger schedule_trigger after update on schedule
for each row when (new. price <> old. price)
begin
dbms_output.put_line( 'the flight number ' || :new.fnumber|| DEPARTURE_APCODE ||DEPARTURE_TIME|| ' has changed to '||:new.price ||' From'||:old.price);
end;

You are referring to the columns of Flight table - DEPARTURE_APCODE and DEPARTURE_TIME directly inside trigger which is not possible.You need to use a select
to fetch those values.
CREATE OR REPLACE TRIGGER schedule_trigger AFTER
UPDATE ON schedule
FOR EACH ROW
WHEN ( new.price <> old.price )
DECLARE
v_departure_apcode flight.departure_apcode%TYPE;
v_arrival_apcode flight.arrival_apcode%TYPE;
BEGIN
SELECT
departure_apcode,
arrival_apcode
INTO
v_departure_apcode,v_arrival_apcode
FROM
flight
WHERE
fnumber =:new.fnumber;
dbms_output.put_line('The flight number '
||:new.fnumber
|| v_departure_apcode
|| v_arrival_apcode
|| ' has changed to '
||:new.price
|| ' From '
||:old.price);
END;
/
And note that using DBMS_OUTPUT.PUT_LINE() inside a trigger is of no use as SQL developer sometimes does not display your output. Better create a log table and insert the records into it in your trigger.
You may require a commit statement for the output to be displayed.
SET SERVEROUTPUT ON;
UPDATE Schedule SET PRICE = 2502.00 where fnumber = 'F2004' ;
commit;
Commit complete.
The flight number F2004AB CD has changed to 2502 From 2501

Related

Update table with a trigger - How to solve mutation error

I'm trying to create a trigger for updating some columns of a record after creating or updating the record.
There my code:
CREATE OR REPLACE TRIGGER "TRIGGER_UPDATE_CONTRACTOR_LOT"
AFTER INSERT OR UPDATE ON "CONTRACTOR_LOT"
FOR EACH ROW
DECLARE
CONTRACT_LOT_LABEL VARCHAR2(255 BYTE);
CONTRACTOR_LABEL VARCHAR2(200 BYTE);
BEGIN
SELECT LABEL INTO CONTRACT_LOT_LABEL FROM LOT_T WHERE ID = :NEW.LOT_ID;
SELECT CONTRACTOR INTO CONTRACTOR_LABEL FROM CONTRACTOR_T WHERE ID = :NEW.CONTRACTOR_ID;
UPDATE CONTRACTOR_LOT
SET LABEL = CONTRACT_LOT_LABEL || ':' || CONTRACTOR_LABEL,
FRAMEWORK_CONTRACT_NUMBER_LABEL = :NEW.ORDER || ':' || CONTRACT_LOT_LABEL || :NEW.FRAMEWORK_CONTRACT_NUMBER
WHERE ID = :NEW.ID;
END;
I get an error ORA-04091 (mutation)
I tried to add PRAGMA AUTONOMOUS_TRANSACTION; and I get the error ORA-00060 (deadlock detected while waiting for resource)
So I add COMMIT; after the update, but it's still the same issue.
Could you help me please with that?
You can't perform DML on the parent table within a trigger, and there's really rarely any reason to do so. Try this, with a BEFORE trigger and just modifying the NEW values of the existing INSERT or UPDATE that is firing the trigger:
CREATE OR REPLACE TRIGGER TRIGGER_UPDATE_CONTRACTOR_LOT
BEFORE INSERT OR UPDATE ON CONTRACTOR_LOT
FOR EACH ROW
DECLARE
CONTRACT_LOT_LABEL VARCHAR2(255 BYTE);
CONTRACTOR_LABEL VARCHAR2(200 BYTE);
BEGIN
SELECT LABEL INTO CONTRACT_LOT_LABEL FROM LOT_T WHERE ID = :NEW.LOT_ID;
SELECT CONTRACTOR INTO CONTRACTOR_LABEL FROM CONTRACTOR_T WHERE ID = :NEW.CONTRACTOR_ID;
:NEW.CONTRACTOR_LOT := CONTRACT_LOT_LABEL || ':' || CONTRACTOR_LABEL;
:NEW.FRAMEWORK_CONTRACT_NUMBER_LABEL := :NEW.ORDER || ':' || CONTRACT_LOT_LABEL || :NEW.FRAMEWORK_CONTRACT_NUMBER;
END;
Your trigger is catching changes and inserts on this table and then update on the same table. It seems to be vicious circle. Because update inside the trigger also will want to initiate this trigger

How to prevent trigger from updating the timestamp on a column in Oracle?

I have a table which has a column on which I have set a trigger to update the timestamp when there is an update on the row
CREATE TABLE mytable (
id NUMBER NOT NULL,
sid VARCHAR(10) NOT NULL,
stext VARCHAR(10),
tid VARCHAR(10),
ttext VARCHAR(10),
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique PRIMARY KEY (id) ENABLE
);
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
select systimestamp into :new.last_updated from dual;
END;
/
However, when certain SQLs are executed on the table, I do NOT want the 'last_updated' column to be updated. For instance, when the below SQL updates this table, I do NOT want the trigger to kick in. I need the column to retain the last_updated value so that my other queries do not get messed up. However, when other SQLs do updates on the data, I want the column to be updated.
Any ideas on how this could be done?
MERGE INTO mytable
USING dual
ON (id = ?)
WHEN MATCHED THEN
UPDATE SET
stext = case when sid = ? then ? else stext end,
ttext = case when tid = ? then ? else ttext end
You can easily keep a supplied value and only insert the current time if it is not supplied:
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
:new.last_updated := coalesce(:new.last_updated, systimestamp);
END;
If you do not want to fire trigger when you update STEXT or TTEXT columns please try this:
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
if (not updating('stext') and not updating('ttext')) then
:new.last_updated := systimestamp;
end if;
END;
Thanks
You can put that specific SQL Statement into a PROCEDURE in which the module info is updated for the concerned application(always call this statement from this procedure only) such as
CREATE OR REPLACE PROCEDURE Merge_Text AS
BEGIN
DBMS_APPLICATION_INFO.SET_MODULE($$plsql_unit,null);
MERGE INTO mytable
USING dual
ON (id = ?)
WHEN MATCHED THEN
UPDATE SET
stext = case when sid = ? then ? else stext end,
ttext = case when tid = ? then ? else ttext end;
END;
/
and then distinguish this caller module within the trigger such as
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
v_module VARCHAR2(80);
v_action VARCHAR2(80);
BEGIN
DBMS_APPLICATION_INFO.READ_MODULE(v_module, v_action);
IF v_module != 'MERGE_TEXT' THEN
:new.last_updated := systimestamp;
END IF;
END;
/

IF(:old.actDepartDateTime IS NULL) THEN

backs up the flight ID, ticket number and seat number for any deleted ticket if the ticket corresponds to a flight that has not yet departed.
CREATE SEQUENCE generateKey
START WITH 100
INCREMENT BY 1;
CREATE TABLE backUpTicket
(
cancelledKey NUMBER NOT NULL,
flightID char(9) NOT NULL,
ticketNum varchar2(5) NOT NULL,
seatNum NUMBER(3) NOT NULL,
PRIMARY KEY(cancelledKey)
);
CREATE OR REPLACE TRIGGER backUpTicketCancelled
BEFORE DELETE ON FLIGHT
FOR EACH ROW
BEGIN
INSERT INTO backUpTicket
VALUES
(generateKey.nextVal, :old.flightID, :old.TicketNum, :old.seatNum);
IF(:old.actDepartDateTime IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('The Flight has not departed yet');
END IF;
END backUpTicket;
/
the error coming in the IF(:old.actDepartDateTime IS NULL) as bad bind operator
First, your trigger should be on the TICKET table, not the FLIGHT table. Second, you're not supposed to create the backup of the ticket if the flight has already departed. Your code always creates the backup. I suggest the following might work better:
CREATE OR REPLACE TRIGGER backUpTicketCancelled
BEFORE DELETE ON TICKET
FOR EACH ROW
DECLARE
rowFlight FLIGHT%ROWTYPE;
BEGIN
SELECT *
INTO rowFlight
FROM FLIGHT f
WHERE f.FLIGHTID = :OLD.FLIGHTID;
IF rowFlight.actDepartDateTime IS NULL THEN
INSERT INTO backUpTicket
(CANCELLEDKEY, FLIGHTID, TICKETNUM, SEATNUM)
VALUES
(generateKey.nextVal, :old.flightID, :old.TicketNum, :old.seatNum);
ELSE
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE', 'DD-MON-YYYY HH24:MI:SS') || ': ' ||
'Flight ' || :OLD.FLIGHTID || ' departed at ' ||
TO_CHAR(rowFlight.actDepartDateTime, 'DD-MON-YYYY HH24:MI:SS' ||
' so ticket #' || :OLD.TICKETNUM || ' for seat #' || :OLD.SEATNUM ||
' is not eligible for backup');
END IF;
END backUpTicket;
Also, the name of the backup table is supposed to be BACKUP_TICKET not backUpTicket, so you'll probably get marked down for that.
In addition, while I realize this is a homework assignment, this is a very poor design choice. You should create a procedure which performs all actions required to delete a ticket, rather than having "magic code" buried in a trigger which performs this kind of business decision.
Best of luck.
From your comments, your trigger is on table TICKET but the column actDepartDateTime is in table FLIGHT. You can only access columns from the trigger's table using :OLD and :NEW. To get actDepartDateTime you will need to select from the FLIGHT table using the :OLD.flightID value.

use the same auto increment trigger in oracle for many tables

I created a database in MySQL with ~10 tables, each starting with the column
SN INT NOT NULL AUTO_INCREMENT
SN doesn't mean anything, just the primary to differentiate between possibly repeating/similar names/titles, etc
I'm moving it to Oracle now, and found this post here on stackoverflow to make the trigger to auto increment the SN field. Basically,
CREATE SEQUENCE user_seq;
CREATE OR REPLACE TRIGGER user_inc
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SELECT user_seq.NEXTVAL
INTO :new.SN
FROM dual;
END;
/
Now, how can I rewrite that trigger once to apply to all the other tables? Because otherwise I have to rewrite it for many tables, just changing the trigger name and sequence name... I was picturing something like:
BEFORE INSERT ON users OR other_table OR another_one
I also found this post here, but the one answer there isn't helpful because I think it's reasonable for many tables to have the same SN field, or I'm misunderstanding the point.
Also, not Oracle 12c so no identity columns
Thanks in advance
I was going to just comment on the first post I mentioned but I can't comment without more reputation points :/
Creating a trigger referencing many tables in Oracle is not possible,
What you can do is to generate the triggers with a PL/SQL statement.
Below is an example on how could you achieve this
drop table tab_a;
drop table tab_b;
drop table tab_c;
drop sequence seq_tab_a_id;
drop sequence seq_tab_b_id;
drop sequence seq_tab_c_id;
--create test tables
create table tab_a (SN number, otherfield varchar2(30), date_field date);
create table tab_b (SN number, otherfield varchar2(30), date_field date);
create table tab_c (SN number, otherfield varchar2(30), date_field date);
-- this pl/sql block creates the sequences and the triggers
declare
my_seq_create_stmt varchar2(2000);
my_trigger_create_stmt varchar2(2000);
begin
for i in (select table_name
from user_tables
-- remember to change this where condition to filter
-- the tables that are relevant for you
where table_name in ('TAB_A', 'TAB_B', 'TAB_C') )loop <<TableLoop>>
my_seq_create_stmt := 'CREATE SEQUENCE '||'SEQ_'||i.table_name||'_ID '
||CHR(13)||' START WITH 1 INCREMENT BY 1 NOCYCLE ';
execute immediate my_seq_create_stmt;
my_trigger_create_stmt := 'CREATE OR REPLACE TRIGGER '||'TRG_'||i.Table_name||'_ID_BI '||' BEFORE INSERT ON '||i.table_name||' FOR EACH ROW '
||CHR(13)||'BEGIN '
||CHR(13)||' SELECT '||'SEQ_'||i.table_name||'_ID'||'.NEXTVAL '
||CHR(13)||' INTO :new.SN '
||CHR(13)||' FROM dual; '
||CHR(13)||'END; ';
execute immediate my_trigger_create_stmt;
end loop TableLoop;
end;
/
-- test the triggers and the sequences
insert into tab_a (otherfield, date_field) values ('test 1',sysdate);
insert into tab_a (otherfield, date_field) values ('test 2',sysdate);
commit;
Select * from tab_a;

ORA-04084: cannot change NEW values for this trigger type

I'm trying to turn pl/sql trigger that calculates the total of some cells in the table when the tale is changed. This is the code:
ALTER session SET nls_date_format='dd/mm/yyyy';
CREATE OR REPLACE TRIGGER TOTAL
AFTER UPDATE OR INSERT ON ORDER_ITEMS
FOR EACH ROW
DECLARE
temp NUMBER;
today DATE;
BEGIN
temp:=(:NEW.item_price-:NEW.discount_amount)*:NEW.quantity;
today := CURRENT_DATE;
:NEW.TOTAL := temp;
dbms_output.put_line('Updated on:' ||today || ' item number: ' ||:NEW.item_id|| 'order number:' ||:NEW.order_id|| 'total: ' ||:NEW.total);
END;
/
show errors
insert into order_items (ITEM_ID, ORDER_ID, PRODUCT_ID, ITEM_PRICE, discount_amount, QUANTITY)
VALUES (13, 7, 3, 553, 209, 2);
And I get this error:
00000 - "cannot change NEW values for this trigger type"
*Cause: New trigger variables can only be changed in before row
insert or update triggers.
*Action: Change the trigger type or remove the variable reference. No Errors. 1 rows inserted Updated on:06/01/2016 item number: 13order
number:7total:
I understand that the problem is updating a table during the trigger execution caused by an update to the same table.
As requested in comments I'm making my comment as an answer.
Your problem is because you are trying to change a value AFTER the value was persisted, try changing your trigger to BEFORE as:
CREATE OR REPLACE TRIGGER TOTAL
BEFORE UPDATE OR INSERT ON ORDER_ITEMS
FOR EACH ROW
DECLARE
temp NUMBER;
today DATE;
BEGIN
temp:=(:NEW.item_price-:NEW.discount_amount)*:NEW.quantity;
today := CURRENT_DATE;
:NEW.TOTAL := temp;
dbms_output.put_line('Updated on:' || today || ' item number: '
|| :NEW.item_id || 'order number:' || :NEW.order_id
|| 'total: ' ||:NEW.total);
END;
/

Resources