How to duplicate records with triggers? - oracle

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>

Related

Hi, I am trying to create a trigger, which automatically calculates col1 + col2 and inserts the sum into col3 after the data insertion

I am trying to create a trigger, which automatically calculates col1 + col2 and inserts the sum into col3 after the data insertion. For some reason there is an error,
Does anyone know how to achieve this?
CREATE OR REPLACE TRIGGER test_tr
AFTER INSERT ON test
FOR EACH ROW
DECLARE
test_one NUMBER(10);
test_two NUMBER(10);
BEGIN
SELECT random
INTO test_one
FROM test
WHERE id = id;
SELECT random2
INTO test_two
FROM test
WHERE id = id;
INSERT INTO test(test3) values ((test_one+test_two));
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
Don't select from the same table as trigger won't see it; table is mutating. Use pseudorecords.
SQL> create table test_one (random number, random2 number, test3 number);
Table created.
SQL> create or replace trigger test_tr
2 before insert on test_one
3 for each row
4 begin
5 :new.test3 := :new.random + :new.random2;
6 end;
7 /
Trigger created.
SQL> insert into test_One (random, random2)
2 select 1, 2 from dual union all
3 select 5, 3 from dual;
2 rows created.
SQL> select * from test_One;
RANDOM RANDOM2 TEST3
---------- ---------- ----------
1 2 3
5 3 8
SQL>
On the other hand, why bother with a trigger? Create a virtual column:
SQL> create table test_one
2 (random number,
3 random2 number,
4 test3 number as (random + random2) virtual
5 );
Table created.
SQL> insert into test_One (random, random2) values (3, 9);
1 row created.
SQL> select * from test_one;
RANDOM RANDOM2 TEST3
---------- ---------- ----------
3 9 12
SQL>
Or, don't create anything and calculate random + random2 when you need it.
Don't use a trigger, from Oracle 11gR1, you can use a virtual column:
ALTER TABLE test ADD test3 NUMBER GENERATED ALWAYS AS (test1 + test2);
Alternatively, when you want to display the test3 value then calculate it in the SELECT clause:
SELECT test1, test2, test1 + test2 AS test3 FROM test;
db<>fiddle here

In Oracle SQL, I would like to call a Oracle stored procedure and then select the value of an OUT parameter as a column result. Is this possible?

CREATE OR REPLACE PROCEDURE myStoredProcedure (idParam IN VARCHAR2,
outputParam OUT VARCHAR2)
AS
BEGIN
SELECT OUTPUTCOL INTO outputParam FROM MyTable WHERE ID = idParam;
END;
DECLARE
v_OutputResults VARCHAR2(20);
BEGIN
myStoredProcedure('123', v_OutputResults);
SELECT v_OutputResults AS ColumnResult FROM DUAL;
END;
If we understand your goal, you should be using a function, not a procedure:
First, we set up the example:
SQL> -- conn system/halftrack#pdb01
SQL> conn scott/tiger#pdb01
Connected.
SQL> --
SQL> CREATE TABLE my_table (
2 user_id number not null,
3 Name varchar2(10)
4 )
5 ;
Table created.
SQL> --
SQL> insert into my_table values (1,'Bob');
1 row created.
SQL> insert into my_table values (2,'Carol');
1 row created.
SQL> insert into my_table values (3,'Ted');
1 row created.
SQL> insert into my_table values (4,'Alice');
1 row created.
SQL> commit;
Commit complete.
SQL> select * from my_table;
USER_ID NAME
---------- ----------
1 Bob
2 Carol
3 Ted
4 Alice
4 rows selected.
Now we create the function, then use it:
SQL> --
SQL> create or replace function my_function (id_param number)
2 return varchar2
3 is
4 v_name varchar2(10);
5 begin
6 select name
7 into v_name
8 from my_table
9 where user_id = id_param
10 ;
11 --
12 return v_name;
13 end;
14 /
Function created.
SQL> show errors
No errors.
SQL> --
SQL> select my_function(1) from dual;
MY_FUNCTION(1)
--------------------------------------------------------------------------------
Bob
1 row selected.
And clean up our example:
SQL> --
SQL> drop table my_table purge;
Table dropped.
No but you can do so using a stored function.

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>

Count of table before and after insert inside a trigger

Is it possible to check the count of a table before any changes happen and the count after the insert and match them inside the same trigger?
for ex: old.count and new.count (before and after insert) ?
old.count and new.count (before and after insert)
Nothing stops you from using SELECT COUNT(*) in a before insert trigger. Of course, you won't do it in a after insert trigger, since a select count(*) on the same table on which an after trigger is defined would throw mutating table error. One way is autonomous transaction. But, in your case, it isn't that complex.
You could define a BEFORE INSERT TRIGGER and take the table count. The after insert count could be taken manually after the actual insert is done.
For example, I have a table t1 with one row. I have defined a before insert trigger on it, which would give me the table count before the insert happens.
SQL> DROP TABLE t1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t1 (A NUMBER);
Table created.
SQL>
SQL> INSERT INTO t1 VALUES (1);
1 row created.
SQL>
SQL> SELECT * FROM t1;
A
----------
1
SQL>
SQL> CREATE OR REPLACE TRIGGER trg
2 BEFORE INSERT
3 ON t1
4 FOR EACH ROW
5
6 DECLARE
7 val number;
8 BEGIN
9 SELECT COUNT(*)
10 INTO val
11 FROM t1;
12
13 DBMS_OUTPUT.PUT_LINE('TABLE COUNT BEFORE INSERT = '||val);
14
15 END;
16 /
Trigger created.
SQL>
SQL> set serveroutput on
SQL> INSERT INTO t1 VALUES (1);
TABLE COUNT BEFORE INSERT = 1
1 row created.
SQL>
SQL> SELECT COUNT(*) FROM t1;
COUNT(*)
----------
2
SQL>
So, you see TABLE COUNT BEFORE INSERT = 1 and then after insert the count is 2.

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;

Resources