Simulating constraint ON UPDATE option by TRIGGER - oracle

I'm trying to simulate ON UPDATE option in my foreign keys by making trigger that will update child records after update.
Trigger looks like this:
CREATE OR REPLACE TRIGGER SOMETABLE_AU_ID_TRG
AFTER UPDATE OF ID ON SOMETABLE
FOR EACH ROW
BEGIN
UPDATE SOMECHILDTABLE SET SOMETABLE_ID = :NEW.ID WHERE ID = :OLD.ID;
END;
And I'm making the child table constraint deferrable like this:
set constraint
SOMECHILDTABLE_FK_SOMETABLE deferred;
But it still gives me ORA-02292 (child record found) on commit. How can I avoid this?

Kudos to #BrianCamire for spotting the bug:
Let's start by creating your trigger...
SQL> CREATE OR REPLACE TRIGGER SOMETABLE_AU_ID_TRG
AFTER UPDATE OF ID ON SOMETABLE
FOR EACH ROW
BEGIN
UPDATE SOMECHILDTABLE SET SOMETABLE_ID = :NEW.ID WHERE ID = :OLD.ID;
END; 2 3 4 5 6
7 /
Trigger created.
SQL> alter table somechildtable add constraint some_fk foreign key (sometable_id)
2 references sometable (id) deferrable initially deferred;
Table altered.
SQL>
Here is the data ...
SQL> select * from somechildtable;
ID SOMETABLE_ID
---------- ------------
11 1
12 2
13 6
14 4
15 5
SQL>
So, let's mess with the parent data....
SQL> update sometable set id = 3 where id = 6
2 /
1 row updated.
SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02292: integrity constraint (APC.SOME_FK) violated - child record found
SQL>
Aha! So, as Brian says we need to change the trigger code:
SQL> CREATE OR REPLACE TRIGGER SOMETABLE_AU_ID_TRG
AFTER UPDATE OF ID ON SOMETABLE
FOR EACH ROW
BEGIN
UPDATE SOMECHILDTABLE SET SOMETABLE_ID = :NEW.ID WHERE SOMETABLE_ID = :OLD.ID;
END;
/
2 3 4 5 6 7
Trigger created.
SQL> SQL> update sometable set id = 3 where id = 6;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from somechildtable
2 /
ID SOMETABLE_ID
---------- ------------
11 1
12 2
13 3
14 4
15 5
SQL>

Related

How to duplicate records with triggers?

CREATE OR REPLACE TRIGGER "DUPLICATE_FOO" AFTER INSERT ON "FOO" FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
insert into remote_foo values(:new); -- can I do this?
EXCEPTION
-- TODO log somewhere
END;
Is there an elegant way to create a trigger that basically duplicates a record from one table to another?
I would like to avoid having to specify the fields of the table since it will mean that the trigger would have to be updated in case there are changes in the schema (the remote scheme would be updated of course). I have like a dozen of tables more.
All the examples I have found always specify the fields in the insert (:new.fieldX).
The keyword here is NOT to specify column names, right? In my opinion, you should because that's the only way you can control it.
the remote scheme would be updated of course
is kind of dangerous. WHAT IF it doesn't happen? "Of course" works until it does not work.
Sample tables (both are empty):
SQL> create table foo (id number, name varchar2(20));
Table created.
SQL> create table remote_foo as select * From foo where 1 = 2;
Table created.
If you use a trigger which is an autonomous transaction, then it won't see :new pseudorecord (as this is an autonomous transaction; right?); to this trigger, select * from foo where id = :new.id; won't return anything and remote_foo remains empty:
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into remote_foo select * from foo where id = :new.id;
8 commit;
9 end;
10 /
Trigger created.
SQL> insert into foo (id, name) values (1, 'Littlefoot');
1 row created.
SQL> select * from foo;
ID NAME
---------- --------------------
1 Littlefoot
SQL> select * from remote_foo; --> it remained empty
no rows selected
SQL>
Note that - if you specified columns - it would work (but that's not what you wanted):
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into remote_foo (id, name) values (:new.id, :new.name);
8 commit;
9 end;
10 /
Trigger created.
SQL> insert into foo (id, name) values (2, 'Bigfoot');
1 row created.
SQL> select * from foo;
ID NAME
---------- --------------------
2 Bigfoot
SQL> select * from remote_foo;
ID NAME
---------- --------------------
2 Bigfoot
SQL>
So, what to do? Switch to a statement-level trigger (instead of a row-level): it doesn't have to be autonomous, but has to have something that will prevent duplicates to be inserted - for example, a NOT EXISTS clause:
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 begin
4 insert into remote_foo
5 select * from foo a
6 where not exists (select null from remote_foo b
7 where b.id = a.id);
8 end;
9 /
Trigger created.
SQL> insert into foo (id, name) values (1, 'Littlefoot');
1 row created.
SQL> insert into foo (id, name)
2 select 2, 'Bigfoot' from dual union all
3 select 3, 'anat0lius' from dual;
2 rows created.
Result:
SQL> select * from foo;
ID NAME
---------- --------------------
2 Bigfoot
3 anat0lius
1 Littlefoot
SQL> select * from remote_foo;
ID NAME
---------- --------------------
1 Littlefoot
3 anat0lius
2 Bigfoot
SQL>

Adding to a column 10 options on PLSQL

I want to add column but it should be 10 options to my database(PL/SQL).
My sql query is look like this:
ALTER TABLE mytable
ADD NEWCOL
Do you think that is it work?
I don't have to think, I know it won't work.
SQL> INSERT_INTO MYTABLE
2 (MYNEW_COL)
3 VALUES
4 (1,2,3,4,5,6,7,8,9,10);
INSERT_INTO MYTABLE
*
ERROR at line 1:
ORA-00900: invalid SQL statement
If you want to insert exactly those values, use a row generator:
SQL> insert into mytable (mynew_col)
2 select level from dual
3 connect by level <= 10;
10 rows created.
SQL> select * from mytable;
MYNEW_COL
----------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
SQL>
Otherwise, discover other valid ways to do that, such as
SQL> insert into mytable (mynew_col)
2 select 1 from dual union all
3 select 2 from dual union all
4 select 3 from dual;
3 rows created.
or
SQL> insert all
2 into mytable (mynew_col) values (1)
3 into mytable (mynew_col) values (2)
4 into mytable (mynew_col) values (3)
5 select * from dual;
3 rows created.
SQL>
[EDIT] Ah, you turned the question upside down. If you want to add a new column and limit number of valid values, then:
SQL> alter table mytable add newcol number;
Table altered.
SQL> alter table mytable add constraint
2 ch_col check (newcol between 1 and 10);
Table altered.
Testing:
SQL> update mytable set newcol = 0;
update mytable set newcol = 0
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CH_COL) violated
SQL> update mytable set newcol = 11;
update mytable set newcol = 11
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CH_COL) violated
SQL> update mytable set newcol = 2;
16 rows updated.
SQL>

How to add extra column in the INTO section of SQL Trigger

How to add an extra column (say column_2) in the below INTO section of my code along with my Column_1. I assume we can do that by adding comma (,) and just add column_2 (like this INTO :new.Column_1, new.column_2). I'm missing something?
create or replace trigger trigger_name
BEFORE INSERT
ON table_name
FOR EACH ROW
BEGIN
SELECT SEQUENCE_NUMBER.NEXTVAL
INTO :new.Column_1
FROM dual;
END;
It is easy to confirm whether you are right (or wrong). I hope you got the answer during the past 6 hours. If not, here's an example:
SQL> create table test
2 (id number,
3 datum date);
Table created.
SQL> create sequence seq_test;
Sequence created.
SQL> create or replace trigger trg_bi_test
2 before insert on test
3 for each row
4 begin
5 select seq_test.nextval, sysdate
6 into :new.id, :new.datum
7 from dual;
8 end;
9 /
Trigger created.
SQL> insert into test (id) values (-1);
1 row created.
SQL> select * From test;
ID DATUM
---------- -------------------
1 21.06.2019 21:54:08
SQL>

Added date and modified date for Oracle table audit

According to requirenments I need to support two mandatory columns for each table: ADDED_DATE, MODIFIED_DATE.
These are intended for DBA/auditing purposes.
Is it possible to make this stuff totally automatic, implicitly supported by DB itself when I'm inserting / updating records from within application?
create a trigger on the table (before update for each row).
SQL> create table foo (hi varchar2(10), added_date date, modified_date date);
Table created.
SQL> create trigger foo_auifer
2 before update or insert on foo
3 for each row
4 declare
5 begin
6 if (inserting) then
7 :new.added_date := sysdate;
8 elsif (updating) then
9 :new.modified_date := sysdate;
10 end if;
11 end;
12 /
Trigger created.
SQL> insert into foo (hi) values ('TEST');
1 row created.
SQL> insert into foo (hi) values ('TEST2');
1 row created.
SQL> update foo set hi = 'MODDED' where rownum = 1;
1 row updated.
SQL> alter session set nls_date_format='dd-mon-yyyy hh24:mi:ss';
Session altered.
SQL> select * from foo;
HI ADDED_DATE MODIFIED_DATE
---------- -------------------- --------------------
MODDED 07-nov-2012 15:28:28 07-nov-2012 15:28:39
TEST2 07-nov-2012 15:28:30
SQL>
create table "db_user"."my_table" (
...
"added_date" date default sysdate,
"modified_date" date default sysdate
)
/
create or replace
trigger "db_user"."trg_my_table_audit"
before update on my_table for each row
begin
:new.modified_date := sysdate;
end;

Compound triggers in oracle

I have a compound trigger and in the after statement I have an update to other table that has also a compound trigger, like in the code below:
create or replace
trigger TRIGGER
for insert or update on TABLE
COMPOUND trigger
after STATEMENT is
begin
update THEOTHERTABLE set VALUE = VALUE + 1 where COD = 1;
end after STATEMENT;
end;
The update is just a simple one to see if works. I want it to fire the trigger on THEOTHERTABLE, but it only fires if the trigger is not compound.
Is this a problem with Oracle compound triggers or just a feature which I am not understanding?
I have attempted to re-create your scenario, and it appears to work fine for me. So I think you should look again at your implementation. Look for the differences between what you have coded and what follows here, and perhaps that is where the answer lies.
Here are my triggers
SQL> create or replace
2 trigger t1_compound
3 for insert or update on t1
4 compound trigger
5
6 after statement is
7 begin
8 update t2 set t1_id = nvl(t1_id,0) + 1 where cod = 12;
9 end after statement;
10 end;
11 /
Trigger created.
SQL>
SQL> create or replace
2 trigger t2_compound
3 for insert or update on t2
4 compound trigger
5
6 after statement is
7 begin
8 update t3 set t2_id = nvl(t2_id,0) + 1 where cod = 12;
9 end after statement;
10 end;
11 /
Trigger created.
SQL>
... here is the test data ...
SQL> select id, cod from t1
2 /
ID COD
---------- ----------
1 12
SQL> select id, cod, t1_id from t2
2 /
ID COD T1_ID
---------- ---------- ----------
11 12
SQL> select id, cod, t2_id from t3
2 /
ID COD T2_ID
---------- ---------- ----------
111 12
SQL>
... and this is what happens when I issue an update on the first table ...
SQL> update t1 set dt = sysdate
2 where id = 1
3 /
1 row updated.
SQL> select id, cod, t1_id from t2
2 /
ID COD T1_ID
---------- ---------- ----------
11 12 1
SQL> select id, cod, t2_id from t3
2 /
ID COD T2_ID
---------- ---------- ----------
111 12 1
SQL>
It works if you do it after compiling the trigger but if you try it again, even with other data, it won't update THEOTHERTABLE
i don't think it should work...it would be better if you would create a procedure for THEOTHERTABLE and call that procedure from this trigger.

Resources