DBA Hub

📋Steps in this guide1/10

Database Triggers Overview

An introduction to database triggers in Oracle.

oracle miscconfigurationintermediate
by OracleDba
16 views
1

The Basics

For a full syntax description of the statement, check out the documentation shown here . The vast majority of the triggers I'm asked to look at use only the most basic syntax, described below. The mandatory or keyword and the optional clause define the timing point for the trigger, which is explained below. There are optional declaration and exception sections, like any other PL/SQL block, if required. The "dml-event" can be one or more of the following. DML triggers can be defined for a combination of DML events by linking them together with the keyword. When a trigger is defined for multiple DML events, event-specific code can be defined using the , , flags. Row level triggers can access new and existing values of columns using the ":NEW.column-name" and ":OLD.column-name" references, bearing in mind the following restrictions. - Row-level triggers : Only ":NEW" references are possible as there is no existing row. - Row-level triggers : Both ":NEW" and ":OLD" references are possible. ":NEW" represents the new value presented in the DML statement that caused the trigger to fire. ":OLD" represents the existing value in the column, prior to the update being applied. - Row-level triggers : Only ":OLD" references are possible as there is no new data presented in the triggering statement, just the existing row that is to be deleted. Triggers can not affect the current transaction, so they can not contain or statements. If you need some code to perform an operation that needs to commit, regardless of the current transaction, you should put it in a stored procedure defined as an autonomous transaction, shown here .

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CREATE [OR REPLACE] TRIGGER schema.trigger-name
{BEFORE | AFTER} dml-event ON table-name
[FOR EACH ROW]
[DECLARE ...]
BEGIN
  -- Your PL/SQL code goes here.
[EXCEPTION ...]
END;
/

INSERT
UPDATE
UPDATE OF column-name[, column-name ...]
DELETE

INSERT OR UPDATE OR DELETE

CREATE OR REPLACE TRIGGER my_test_trg
BEFORE INSERT OR UPDATE OR DELETE ON my_table
FOR EACH ROW
BEGIN
  -- Flags are booleans and can be used in any branching construct.
  CASE
    WHEN INSERTING THEN
      -- Include any code specific for when the trigger is fired from an INSERT.
      -- Also fired for INSERT as part of a MERGE.
    WHEN UPDATING THEN
      -- Include any code specific for when the trigger is fired from an UPDATE.
      -- Also fired for UPDATE as part of a MERGE.
    WHEN DELETING THEN
      -- Include any code specific for when the trigger is fired from a DELETE.
      -- Does not fire for DELETE clause of a MERGE.
  END CASE;
END;
/
2

Timing Points

DML triggers have four basic timing points for a single table. - Before Statement : Trigger defined using the keyword, but the clause is omitted. - Before Each Row : Trigger defined using both the keyword and the clause. - After Each Row : Trigger defined using both the keyword and the clause. - After Statement : Trigger defined using the keyword, but the clause is omitted. Oracle allows you to have multiple triggers defined for a single timing point, but it doesn't guarantee execution order unless you use the clause available in Oracle 11g, described here . With the exception of Compound Triggers , the triggers for the individual timing points are self contained and can't automatically share state or variable information. The workaround for this is to use variables defined in packages to store information that must be in scope for all timing points. The following code demonstrates the order in which the timing points are fired. It creates a test table, a package to hold shared data and a trigger for each of the timing points. Each trigger extends a collection defined in the package and stores a message with the trigger name and the current action it was triggered with. In addition, the after statement trigger displays the contents of the collection and empties it. Querying the view shows us the object are present and valid. The follow output shows the contents of the collection after each individual DML statement. From this we can see there is a single statement level before and after timing point, regardless of how many rows the individual statement touches, as well as a row level timing point for each row touched by the statement. The same is true for an " ... " statement. The output for versions up to and including 11gR2 look like the following. From 12cR1 onward, the timing points have changed.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
DROP TABLE trigger_test;
CREATE TABLE trigger_test (
  id           NUMBER         NOT NULL,
  description  VARCHAR2(50)   NOT NULL
);


CREATE OR REPLACE PACKAGE trigger_test_api AS

TYPE t_tab IS TABLE OF VARCHAR2(50);
g_tab t_tab := t_tab();
  
END trigger_test_api;
/

-- BEFORE STATEMENT
CREATE OR REPLACE TRIGGER trigger_test_bs_trg
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
BEGIN
  trigger_test_api.g_tab.extend;
  CASE
    WHEN INSERTING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE STATEMENT - INSERT';
    WHEN UPDATING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE STATEMENT - UPDATE';
    WHEN DELETING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE STATEMENT - DELETE';
  END CASE;
END;
/

-- BEFORE ROW
CREATE OR REPLACE TRIGGER trigger_test_br_trg
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW
BEGIN
  trigger_test_api.g_tab.extend;
  CASE
    WHEN INSERTING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE EACH ROW - INSERT (new.id=' || :new.id || ')';
    WHEN UPDATING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE EACH ROW - UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
    WHEN DELETING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'BEFORE EACH ROW - DELETE (old.id=' || :old.id || ')';
  END CASE;
END trigger_test_br_trg;
/

-- AFTER ROW
CREATE OR REPLACE TRIGGER trigger_test_ar_trg
AFTER INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW
BEGIN
  trigger_test_api.g_tab.extend;
  CASE
    WHEN INSERTING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - INSERT (new.id=' || :new.id || ')';
    WHEN UPDATING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
    WHEN DELETING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - DELETE (old.id=' || :old.id || ')';
  END CASE;
END trigger_test_ar_trg;
/

-- AFTER STATEMENT
CREATE OR REPLACE TRIGGER trigger_test_as_trg
AFTER INSERT OR UPDATE OR DELETE ON trigger_test
BEGIN
  trigger_test_api.g_tab.extend;
  CASE
    WHEN INSERTING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER STATEMENT - INSERT';
    WHEN UPDATING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER STATEMENT - UPDATE';
    WHEN DELETING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER STATEMENT - DELETE';
  END CASE;
  
  FOR i IN trigger_test_api.g_tab.first .. trigger_test_api.g_tab.last LOOP
    DBMS_OUTPUT.put_line(trigger_test_api.g_tab(i));
  END LOOP;
  trigger_test_api.g_tab.delete;
END trigger_test_as_trg;
/

COLUMN object_name FORMAT A20

SELECT object_name, object_type, status FROM user_objects;

OBJECT_NAME          OBJECT_TYPE         STATUS
-------------------- ------------------- -------
TRIGGER_TEST_API     PACKAGE             VALID
TRIGGER_TEST         TABLE               VALID
TRIGGER_TEST_BS_TRG  TRIGGER             VALID
TRIGGER_TEST_BR_TRG  TRIGGER             VALID
TRIGGER_TEST_AR_TRG  TRIGGER             VALID
TRIGGER_TEST_AS_TRG  TRIGGER             VALID

6 rows selected.

SQL>

SQL> SET SERVEROUTPUT ON

SQL> INSERT INTO trigger_test VALUES (1, 'ONE');
BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=1)
AFTER STATEMENT - INSERT

1 row created.

SQL> INSERT INTO trigger_test VALUES (2, 'TWO');
BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=2)
AFTER EACH ROW - INSERT (new.id=2)
AFTER STATEMENT - INSERT

1 row created.

SQL> UPDATE trigger_test SET id = id;
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=2 old.id=2)
AFTER EACH ROW - UPDATE (new.id=2 old.id=2)
BEFORE EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER STATEMENT - UPDATE

2 rows updated.

SQL> DELETE FROM trigger_test;
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=2)
AFTER EACH ROW - DELETE (old.id=2)
BEFORE EACH ROW - DELETE (old.id=1)
AFTER EACH ROW - DELETE (old.id=1)
AFTER STATEMENT - DELETE

2 rows deleted.

SQL> ROLLBACK;

Rollback complete.

SQL>

SET SERVEROUTPUT ON

INSERT INTO trigger_test
SELECT level, 'Description for ' || level
FROM   dual
CONNECT BY level <= 5;

BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=1)
BEFORE EACH ROW - INSERT (new.id=2)
AFTER EACH ROW - INSERT (new.id=2)
BEFORE EACH ROW - INSERT (new.id=3)
AFTER EACH ROW - INSERT (new.id=3)
BEFORE EACH ROW - INSERT (new.id=4)
AFTER EACH ROW - INSERT (new.id=4)
BEFORE EACH ROW - INSERT (new.id=5)
AFTER EACH ROW - INSERT (new.id=5)
AFTER STATEMENT - INSERT

5 rows created.

SQL> ROLLBACK;

Rollback complete.

SQL>

SET SERVEROUTPUT ON

INSERT INTO trigger_test
SELECT level, 'Description for ' || level
FROM   dual
CONNECT BY level <= 5;

BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
BEFORE EACH ROW - INSERT (new.id=2)
BEFORE EACH ROW - INSERT (new.id=3)
BEFORE EACH ROW - INSERT (new.id=4)
BEFORE EACH ROW - INSERT (new.id=5)
AFTER EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=2)
AFTER EACH ROW - INSERT (new.id=3)
AFTER EACH ROW - INSERT (new.id=4)
AFTER EACH ROW - INSERT (new.id=5)
AFTER STATEMENT - INSERT

5 rows created.

SQL> ROLLBACK;

Rollback complete.

SQL>
3

Bulk Binds

In the previous section we've seen what the timing points look like for individual statements. So are they the same for bulk binds? That depends on whether you are doing bulk inserts, updates or deletes using the statement. The following code builds a collection of 5 records, then uses that to drive bulk inserts, updates and deletes on the TRIGGER_TEST table. The triggers from the previous section will reveal the timing points that are triggered. The output from this code is shown below. This is consistent up to and including 11gR2. Notice how the statement level triggers only fire once at the start and end of the bulk insert operation, but fire on a row-by-row basis for the bulk update and delete operations. From 12cR1 onward we see a different order of the timing points for the bulk-bind insert, which reflects the change to the timing points in the DML bulk insert. Make sure you understand your timing points when using bulk binds or you may get unexpected results.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
SET SERVEROUTPUT ON
DECLARE
  TYPE t_trigger_test_tab IS TABLE OF trigger_test%ROWTYPE;
  l_tt_tab t_trigger_test_tab := t_trigger_test_tab();
BEGIN
  FOR i IN 1 .. 5 LOOP
    l_tt_tab.extend;
    l_tt_tab(l_tt_tab.last).id := i;
    l_tt_tab(l_tt_tab.last).description := 'Description for ' || i;
  END LOOP;
  
  DBMS_OUTPUT.put_line('*** FORALL - INSERT ***');
  -- APPEND_VALUES hint is an 11gR2 feature, but doesn't affect timing points.
  FORALL i IN l_tt_tab.first .. l_tt_tab.last
    INSERT /*+ APPEND_VALUES */ INTO trigger_test VALUES l_tt_tab(i);
    
  DBMS_OUTPUT.put_line('*** FORALL - UPDATE ***');
  -- Referencing collection columns in FORALL is only supported in 11g.
  FORALL i IN l_tt_tab.first .. l_tt_tab.last
    UPDATE trigger_test SET description = l_tt_tab(i).description WHERE id = l_tt_tab(i).id;
    
  DBMS_OUTPUT.put_line('*** FORALL - DELETE ***');
  -- Referencing collection columns in FORALL is only supported in 11g.
  FORALL i IN l_tt_tab.first .. l_tt_tab.last
    DELETE FROM trigger_test WHERE id = l_tt_tab(i).id;
    
  ROLLBACK;
END;
/

*** FORALL - INSERT ***
BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=1)
BEFORE EACH ROW - INSERT (new.id=2)
AFTER EACH ROW - INSERT (new.id=2)
BEFORE EACH ROW - INSERT (new.id=3)
AFTER EACH ROW - INSERT (new.id=3)
BEFORE EACH ROW - INSERT (new.id=4)
AFTER EACH ROW - INSERT (new.id=4)
BEFORE EACH ROW - INSERT (new.id=5)
AFTER EACH ROW - INSERT (new.id=5)
AFTER STATEMENT - INSERT
*** FORALL - UPDATE ***
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=2 old.id=2)
AFTER EACH ROW - UPDATE (new.id=2 old.id=2)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=3 old.id=3)
AFTER EACH ROW - UPDATE (new.id=3 old.id=3)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=4 old.id=4)
AFTER EACH ROW - UPDATE (new.id=4 old.id=4)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=5 old.id=5)
AFTER EACH ROW - UPDATE (new.id=5 old.id=5)
AFTER STATEMENT - UPDATE
*** FORALL - DELETE ***
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=1)
AFTER EACH ROW - DELETE (old.id=1)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=2)
AFTER EACH ROW - DELETE (old.id=2)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=3)
AFTER EACH ROW - DELETE (old.id=3)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=4)
AFTER EACH ROW - DELETE (old.id=4)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=5)
AFTER EACH ROW - DELETE (old.id=5)
AFTER STATEMENT - DELETE

PL/SQL procedure successfully completed.

SQL>

*** FORALL - INSERT ***
BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
BEFORE EACH ROW - INSERT (new.id=2)
BEFORE EACH ROW - INSERT (new.id=3)
BEFORE EACH ROW - INSERT (new.id=4)
BEFORE EACH ROW - INSERT (new.id=5)
AFTER EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=2)
AFTER EACH ROW - INSERT (new.id=3)
AFTER EACH ROW - INSERT (new.id=4)
AFTER EACH ROW - INSERT (new.id=5)
AFTER STATEMENT - INSERT
*** FORALL - UPDATE ***
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER EACH ROW - UPDATE (new.id=1 old.id=1)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=2 old.id=2)
AFTER EACH ROW - UPDATE (new.id=2 old.id=2)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=3 old.id=3)
AFTER EACH ROW - UPDATE (new.id=3 old.id=3)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=4 old.id=4)
AFTER EACH ROW - UPDATE (new.id=4 old.id=4)
AFTER STATEMENT - UPDATE
BEFORE STATEMENT - UPDATE
BEFORE EACH ROW - UPDATE (new.id=5 old.id=5)
AFTER EACH ROW - UPDATE (new.id=5 old.id=5)
AFTER STATEMENT - UPDATE
*** FORALL - DELETE ***
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=1)
AFTER EACH ROW - DELETE (old.id=1)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=2)
AFTER EACH ROW - DELETE (old.id=2)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=3)
AFTER EACH ROW - DELETE (old.id=3)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=4)
AFTER EACH ROW - DELETE (old.id=4)
AFTER STATEMENT - DELETE
BEFORE STATEMENT - DELETE
BEFORE EACH ROW - DELETE (old.id=5)
AFTER EACH ROW - DELETE (old.id=5)
AFTER STATEMENT - DELETE

PL/SQL procedure successfully completed.

SQL>
4

How Exceptions Affect Timing Points

If an exception is raised by the DML itself or by the trigger code, no more timing points are triggered. This means the after statement trigger is not fired, which can be a problem if you are using the after statement timing point to do some important processing. To demonstrate this we will force an exception in the after row trigger. When we perform an insert against the table we can see the expected error, but notice there is no timing point information displayed. This is because the after statement trigger did not fire. This also means that the collection was never cleared down. The following code will display the contents of the collection and clear it down. So all timing points executed as expected until the exception was raised, then the statement just stopped, without firing the after statement trigger. If the after statement trigger was responsible for anything important, like cleaning up the contents of the collection, we are in trouble. So once again, make sure you understand how the timing points are triggered, or you could get unexpected behavior.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
CREATE OR REPLACE TRIGGER trigger_test_ar_trg
AFTER INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW
BEGIN
  trigger_test_api.g_tab.extend;
  CASE
    WHEN INSERTING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - INSERT (new.id=' || :new.id || ')';
    WHEN UPDATING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
    WHEN DELETING THEN
      trigger_test_api.g_tab(trigger_test_api.g_tab.last) := 'AFTER EACH ROW - DELETE (old.id=' || :old.id || ')';
  END CASE;
RAISE_APPLICATION_ERROR(-20000, 'Forcing an error.');
END trigger_test_ar_trg;
/

SET SERVEROUTPUT ON

INSERT INTO trigger_test VALUES (1, 'ONE');
            *
ERROR at line 1:
ORA-20000: Forcing an error.
ORA-06512: at "TEST.TRIGGER_TEST_AR_TRG", line 11
ORA-04088: error during execution of trigger 'TEST.TRIGGER_TEST_AR_TRG'

SQL>

BEGIN
  FOR i IN trigger_test_api.g_tab.first .. trigger_test_api.g_tab.last LOOP
    DBMS_OUTPUT.put_line(trigger_test_api.g_tab(i));
  END LOOP;
  trigger_test_api.g_tab.delete;
END;
/

BEFORE STATEMENT - INSERT
BEFORE EACH ROW - INSERT (new.id=1)
AFTER EACH ROW - INSERT (new.id=1)

PL/SQL procedure successfully completed.

SQL>
5

Mutating Table Exceptions

Row-level DML triggers are not allowed to query or perform any DML on the table that fired them. If they attempt to do so a mutating table exception is raised. This can become a little awkward when you have a parent-child relationship and a trigger on the parent table needs to execute some DML on the child table. If the child table has a foreign key (FK) back to the parent table, any DML on the child table will cause a recursive SQL statement to check the constraint. This will indirectly cause a mutating table exception. An example of mutating tables and a workaround for them can be found here .
6

Compound Triggers

Oracle 11g introduced the concept of compound triggers, which consolidate the code for all the timing points for a table, along with a global declaration section into a single code object. The global declaration section stays in scope for all timing points and is cleaned down when the statement has finished, even if an exception occurs. An article about compound triggers and other trigger-related new features in 11g can be found here .
7

Should you use triggers at all? (Facts, Thoughts and Opinions)

I'm not a major fan of DML triggers, but I invariably use them on most systems. Here are a random selection of facts, thoughts and opinions based on my experience. Feel free to disagree. - Adding DML triggers to tables affects the performance of DML statements on those tables. Lots of sites disable triggers before data loads then run cleanup jobs to "fill in the gaps" once the data loads are complete. If you care about performance, go easy on triggers. - Doing non-transactional work in triggers (autonomous transactions, package variables, messaging and job creation) can cause problems when Oracle performs DML restarts. Be aware that a single DML statement may be restarted by the server, causing any triggers to fire multiple times for a single DML statement. If non-transactional code is included in triggers, it will not be rolled back with the DML before the restart, so it will execute again when the DML is restarted. - If you must execute some large, or long-running, code from a trigger, consider decoupling the process. Get your trigger to create a job or queue a message, so the work can by picked up and done later. - Spreading functionality throughout several triggers can make it difficult for developers to see what is really going on when they are coding, since their simple insert statement may actually be triggering a large cascade of operations without their knowledge. This can quickly lead to spaghetti solutions. - It can be hard to track code dependencies when using triggers. See example below . - Triggers inevitably get disabled by accident and their "vital" functionality is lost so you have to repair the data manually. - If something is complex enough to require one or more triggers, you should probably place that functionality in a PL/SQL API and call that from your application, rather than issuing a DML statement and relying on a trigger to do the extra work for you. PL/SQL doesn't have all the restrictions associated with triggers, so it's a much nicer solution. - I've conveniently avoided mentioning triggers up until now. I'm not saying they have no place and should be totally avoided, but if you find yourself using them a lot, you should probably either redesign your system, or use PL/SQL APIs rather than triggers. One place I have used them a lot was in a system with lots of object-relational functionality. Also another feature whose usage should be questioned. Toon Koppelaars has a whole blog dedicated to database triggers here , where he discusses sensible use-cases for triggers and provides a counter argument for people who dislike triggers, like me. :)
8

The Code Dependency Problem

The use of triggers can make tracking the dependencies of your code problematic. It's probably easiest to explain this with a simple example. The following code creates a table (T1), sequence (T1_SEQ) and procedure (P1) to insert into the T1 table. It then runs the procedure twice and checks the contents of the T1 table. Check the dependencies of the P1 procedure using the view ( code_dep.sql ). Notice the P1 procedure is dependent on the T1 table and the T1_SEQ sequence, which is what we would expect. The following code creates a new table (T2), then adds a trigger to the T1 table that inserts into the T2 table. Finally, it runs the original P1 procedure and checks the output of the T1 and T2 tables. We can see the P1 procedure is now indirectly dependent on T2 table. Check the dependencies of the P1 procedure again. The P1 procedure is now indirectly dependent on the T2 table, but it is not obvious from code dependencies. Now imagine diagnosing a problem where triggers create multiple dependencies that are *not* obvious when you look at the dependencies displayed in SQL*Plus, SQL Developer, TOAD etc. I think you can see the confusion this can cause.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
-- Clean up.
DROP TABLE t1 PURGE;
DROP TABLE t2 PURGE;
DROP SEQUENCE t1_seq;
DROP PROCEDURE p1;

-- Create object.
CREATE TABLE t1 (
  id NUMBER
);

CREATE SEQUENCE t1_seq;

CREATE OR REPLACE PROCEDURE p1 (p_commit IN BOOLEAN DEFAULT TRUE) AS
BEGIN
  INSERT INTO t1 (id) VALUES (t1_seq.nextval);
  
  IF p_commit THEN
    COMMIT;
  END IF;
END;
/

-- Create some data using procedure.
BEGIN
  p1(p_commit => FALSE);
  p1;
END;
/

-- Check the contents of the test table.
SELECT * FROM t1;

        ID
----------
         1
         2

2 rows selected.

SQL>

SQL> @code_dep test p1

REFERENCED_TYPE      REFERENCED_OWNER     REFERENCED_NAME                          REFERENCED_LINK_NAME
-------------------- -------------------- ---------------------------------------- --------------------
PACKAGE              SYS                  STANDARD
                     SYS                  SYS_STUB_FOR_PURITY_ANALYSIS

SEQUENCE             TEST                 T1_SEQ

TABLE                TEST                 T1


4 rows selected.

SQL>

-- Create new objects
CREATE TABLE t2 (
  id NUMBER
);

CREATE OR REPLACE TRIGGER t1_after_insert_trg
  AFTER INSERT ON t1
  FOR EACH ROW
BEGIN
  INSERT INTO t2 (id) VALUES (:new.id);
END;
/

-- Run procedure again
BEGIN
  p1;
END;
/

-- Check contents of tables.
SELECT * FROM t1;

        ID
----------
         1
         2
         3

3 rows selected.

SQL>

SELECT * FROM t2;

        ID
----------
         3

1 row selected.

SQL>

@code_dep test p1

REFERENCED_TYPE      REFERENCED_OWNER     REFERENCED_NAME                          REFERENCED_LINK_NAME
-------------------- -------------------- ---------------------------------------- --------------------
PACKAGE              SYS                  STANDARD
                     SYS                  SYS_STUB_FOR_PURITY_ANALYSIS

SEQUENCE             TEST                 T1_SEQ

TABLE                TEST                 T1


4 rows selected.

SQL>
9

Non-DML (Event) Triggers

Non-DML triggers, also known as event and system triggers, are can be split into two categories: DDL events and database events. The syntax for both are similar, with the full syntax shown here and a summarized version below. A single trigger can be used for multiple events of the same type (DDL or database). The trigger can target a single schema or the whole database. Granular information about triggering events can be retrieved using event attribute functions. - Event Attribute Functions - Event Attribute Functions for Database Event Triggers - Event Attribute Functions for Client Event Triggers Valid events are listed below. For a full description click the link. - DDL Events : - Database Events : Of all the non-DML triggers, the one I use the most is the trigger. Amongst other things, this is is really handy for setting the flag for an application user session.

Code/Command (click line numbers to comment):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE [OR REPLACE] TRIGGER trigger-name
{ BEFORE | AFTER } event [OR event]...
ON { [schema.] SCHEMA | DATABASE }
[DECLARE ...]
BEGIN
  -- Your PL/SQL code goes here.
[EXCEPTION ...]
END;
/

CREATE OR REPLACE TRIGGER app_user.after_logon_trg
AFTER LOGON ON app_user.SCHEMA
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET current_schema=SCHEMA_OWNER';
END;
/
10

Enabling/Disabling Triggers

Prior to Oracle 11g, triggers are always created in the enabled state. In Oracle 11g, triggers can now be created in the disabled state, shown here . Specific triggers are disabled and enabled using the ALTER TRIGGER command. All triggers for a table can be disabled and enabled using the ALTER TABLE command. For more information see: Hope this helps. Regards Tim...

Code/Command (click line numbers to comment):

1
2
3
4
5
ALTER TRIGGER trigger-name DISABLE;
ALTER TRIGGER trigger-name ENABLE;

ALTER TABLE table-name DISABLE ALL TRIGGERS;
ALTER TABLE table-name ENABLE ALL TRIGGERS;

Comments (0)

Please to add comments

No comments yet. Be the first to comment!