Trigger to update another field in same table - sql

I want to run a trigger when I update a certain field on the database, so it updates another field (basically we have 2 different unique IDs for each record, so when one is changed, we need to update the other too - yay!)
CREATE OR REPLACE TRIGGER trigger_name ON table AFTER
UPDATE AS
UPDATE table A
SET unique_to_update = NVL(
(SELECT b.unique_to_update_from
FROM table b
WHERE B.other_unique_id = A.unique_id_to_match
), 0);
I have no idea if this works (scared to test it, quite frankly, since I'm certain it'll break things) and even if it did, it'd run on every single update of that table - not just the one field that I wanted.
Any help would be much appreciated, thank you!

Test anything before putting it in production.
Something like this shoud be your trigger:
CREATE OR REPLACE TRIGGER trigger_name
BEFORE UPDATE of unique_id_to_match
ON table
FOR EACH ROW
AS
BEGIN
select
NVL(
(SELECT b.unique_to_update_from
FROM table b
WHERE B.other_unique_id = :new.unique_id_to_match
), 0)
into :new.unique_to_update
FROM dual;
END;

5000 rows x 900 columns is not so big :)
I was afraid it has 10M of rows :)
OK start
create temporary table tmp_my_important_columns on commit delete rows
as
select unique_id_to_match, unique_to_update_from,
other_unique_id , unique_to_update
from table where rownum < 1;
second
CREATE OR REPLACE TRIGGER trigger_before_upd_stmt
BEFORE UPDATE
ON table
AS
BEGIN
insert into
tmp_my_important_columns
select unique_id_to_match, unique_to_update_from,
other_unique_id , unique_to_update
from table;
END;
third,
CREATE OR REPLACE TRIGGER trigger_name
BEFORE UPDATE of unique_id_to_match
ON table
FOR EACH ROW
AS
BEGIN
select
NVL(
(SELECT b.unique_to_update_from
FROM tmp_my_important_columns b
WHERE B.other_unique_id = :new.unique_id_to_match
), 0)
into :new.unique_to_update
FROM dual;
END;
Comments:
After an update, if you update the same rows again, without commit
(which deletes the tmp table), you'll get problems. So, or you commit
after update, or you can add an after update trigger(without for each row) that deletes all, from tmp table, but this is somehow ugly.
You can add indexes on the temporary table.

Related

Values of the inserted row in a Trigger Oracle

I want a trigger that updates the value of a column, but I just want to update a small set of rows that depends of the values of the inserted row.
My trigger is:
CREATE OR REPLACE TRIGGER example
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
UPDATE table1 t
SET column2 = 3
WHERE t.column1 = :new.column1;
END;
/
But as I using FOR EACH ROW I have a problem when I try it, I get the mutating table runtime error.
Other option is not to set the FOR EACH ROW, but if I do this, I dont know the inserted "column1" for comparing (or I dont know how to known it).
What can I do for UPDATING a set of rows that depends of the last inserted row?
I am using Oracle 9.
You should avoid the DML statements on the same table as defined in a trigger. Use before DML to change values of the current table.
create or replace trigger example
before insert on table1
for each row
begin
:new.column2 := 3;
end;
/
You can modify the same table with pragma autonomous_transaction:
create or replace trigger example
after insert on table1 for each row
declare
procedure setValues(key number) is
pragma autonomous_transaction;
begin
update table1 t
set column2 = 3
where t.column1 = key
;
end setValues;
begin
setValues(:new.column1);
end;
/
But I suggest you follow #GordonLinoff answere to your question - it's a bad idea to modify the same table in the trigger body.
See also here
If you need to update multiple rows in table1 when you are updating one row, then you would seem to have a problem with the data model.
This need suggests that you need a separate table with one row per column1. You can then fetch the value in that table using join. The trigger will then be updating another table, so there will be no mutation problem.
`create table A
(
a INTEGER,
b CHAR(10)
);
create table B
(
b CHAR (10),
d INTEGER
);
create trigger trig1
AFTER INSERT ON A
REFERENCING NEW AS newROW
FOR EACH ROW
when(newROW.a<=10)
BEGIN
INSERT into B values(:newROW.b,:newROW.a);
END trig1;
insert into A values(11,'Gananjay');
insert into A values(5,'Hritik');
select * from A;
select * from B;`

update same table with condition in oracle

Unable to execute the trigger, Can anyone explain where i am doing wrong.
CREATE OR REPLACE TRIGGER HK_WS_ED_CT_T1
BEFORE INSERT OR UPDATE OF CI On HK_WS_ED_CT
FOR EACH ROW
BEGIN
IF (:new.CI = CI) THEN
UPDATE HK_WS_ED_CT
SET (TRANS_OTHER, TRANS_OTHER_DESC) =
(SELECT TRANS_OTHER, TRANS_OTHER_DESC
FROM HK_WS_ED_CT
where CI = :NEW.CI and rownum <=1 order by DATE)
end if;
END;
You are updating the table the trigger is on. This is not allowed.
You should put :new.trans_other equal to something and :new.trans_other_desc to something.
The update of the field will be ok then.
If you really want to update all the rows of that same table you should take a look at compound triggers. But I doubt this is what you want.
The main problem in DB triggers is you can't select, insert, update, delete from the table the trigger is firing on. The table is locked at this moment.

How to create mirror table in oracle by using triggers?

I've created a trigger as follows:
create or replace trigger "PASSENGERS_BACKUP_T1"
after
insert or update or delete on "PASSENGERS"
for each row
begin
if :NEW."P_ID" is NOT null then
INSERT INTO PASSENGERS_BACKUP(
PB_ID,
PB_FIRST_NAME,
PB_LAST_NAME,
PB_STREET_ADDRESS1,
PB_STREET_ADDRESS2,
PB_CITY,
PB_STATE,
PB_POSTAL_CODE,
PB_EMAIL,
PB_PHONE_NUMBER1,
PB_PHONE_NUMBER2,
PB_URL,
PB_CREDIT_LIMIT,
PB_TAGS)
VALUES (
:new.P_ID,
:new.P_FIRST_NAME,
:new.P_LAST_NAME,
:new.P_STREET_ADDRESS1,
:new.P_STREET_ADDRESS2,
:new.P_CITY,
:new.P_STATE,
:new.P_POSTAL_CODE,
:new.P_EMAIL,
:new.PHONE_NUMBER1,
:new.PHONE_NUMBER1,
:new.URL,
:new.CREDIT_LIMIT,
:new.TAGS);
end if;
end;
now, when I update​ an existing row in "passengers" table as per the above trigger another new row is getting added in "passengers_backup" table instead I would like to update the existing row whenever an update is done in "passengers" table rows. As, well If I delete a row in "Passengers" table, if that row exists in 'Passengers_backup' table it should also get deleted. How can I acheive this?
Thanks in advance.
For solving your problem you need to use trigger with corresponding SQL statement for each action: insert, update, delete. As variant you can use something like this (Note, I left only two columns from your example for readability, so modify your trigger as you need):
create or replace trigger "PASSENGERS_BACKUP_TIUD"
after insert or update or delete on "PASSENGER"
for each row
begin
if inserting then
insert into "PASSENGER_BACKUP" (pb_id, pb_first_name)
values (:NEW.pb_id, :NEW.pb_first_name);
elsif updating then
update "PASSENGER_BACKUP"
set pb_id=:NEW.pb_id, pb_first_name=:NEW.pb_first_name
where pb_id=:NEW.pb_id;
elsif deleting then
delete from "PASSENGER_BACKUP"
where pb_id=:OLD.pb_id;
end if;
end;
Also you can see work of this trigger in action on SQL Fiddle.

need to write a trigger

I want to write a trigger for a table "TRANSACTION".When a new line is inserted, I want to trigger to update the field "TRANSACTIONID" to the maximum + 1 of all the previous records.
I on't know much about SQL. Can someone help me?
many thanks
This is a really bad idea for a multi-user environment, as it will serialise inserts into the table. The usual approach is to use an Oracle sequence:
create sequence transaction_seq;
create trigger transaction_bir before insert on transaction
for each row
begin
:new.id := transaction_seq.nextval;
end;
To write a trigger based solution that actually got the max current value plus 1, you would need to write a complex 3-trigger solution to avoid the "mutating table" issue. Or you could create a simpler solution using another table to hold the current maximum value like this:
create table transaction_max (current_max_id number);
insert into transaction_max values (0);
create trigger transaction_bir before insert on transaction
for each row
declare
l_current_max_id number;
begin
update transaction_max set current_max_id = current_max_id + 1
returning current_max_id into l_current_max_id;
:new.id := l_current_max_id;
end;
This will avoid the mutating table issue and will serialize (slow down) inserts, so I don't see any advantage of this over using a sequence.
CREATE TRIGGER trigger1 on TransactionTable
INSTEAD OF INSERT
AS
BEGIN
DECLARE #MaxTranId INT
SELECT
#MaxTranId = MAX(TransactionId)
FROM
TransactionTable
INSERT INTO TransactionTable
SELECT
#MaxTranId + 1 ,
RestOfYourInsertedColumnsHere ,
FROM
inserted
END
GO

Is it possible to move a record from one table to another using a single SQL statement?

I need a query to move a record from one table to another without using multiple statements?
No, you cannot move records in one SQL statement. You have to use an INSERT followed by a DELETE statement. You should wrap these statements into a transaction, to make sure that the copy operation remains atomic.
START TRANSACTION;
INSERT INTO
new_table
SELECT
*
FROM
old_table
WHERE
some_field = 'your_criteria';
DELETE FROM old_table WHERE some_field = 'your_criteria';
COMMIT;
If you really want to do this in a single SQL statement, one way to accomplish this would be to create an "after delete" trigger on the source table that inserts the row into the target table. This way you can move the row from the source table to the target table simply by deleting it from the source table. Of course this will only work if you want to insert into target table for every delete on the source table.
DELIMITER $$
DROP TRIGGER IF EXISTS TR_A_DEL_SOURCE_TABLE $$
CREATE TRIGGER TR_A_DEL_SOURCE_TABLE AFTER DELETE ON SOURCE_TABLE FOR EACH ROW BEGIN
INSERT IGNORE INTO TARGET_TABLE(id,val1,val2) VALUES(old.id,old.va1,old.val2);
END $$
DELIMITER ;
So to move the row with id 42 from source table to target table:
delete from source_table where id = 42;
No - you might be able to do the INSERT in one nested statement, but you still need to remove the record.
There is no way to make it a single query, but if you HAVE to do it in a single query within an application you can create a Stored Procedure to do it for you.
DELIMITER $$
DROP PROCEDURE IF EXISTS `copydelete` $$
CREATE PROCEDURE `copydelete` (id INT)
BEGIN
INSERT INTO New_Table SELECT * from Old_Table WHERE Old_Table.IdField=id;
DELETE FROM Old_Table where IdField=id;
END $$
DELIMITER ;
Then you're new query is just
CALL copydelete(4);
Which will delete WHERE IdField=4;
Please note that the time delay between insert-select and delete can cause you to delete to much.
For a safe route you could use an update field:
update old_table set move_flag=1 where your_criteria
insert into ... select from ... where move_flag = 1
delete from old_table where move_flag=1
Or use a transaction which locks the old_table so no data can be added between insert... select and delete.