In earlier versions of Oracle, triggers were the database objects whose ability to collide with the current database state of an object and session state maintenance, kept the database developers alert while designing triggers. Oracle 11g offers considerable improvements in database triggers. These additions have not only enhanced the language usability but also promise a better performance in a real application environment. Oracle 11g fixes major issues by the enhancements as listed below.
- Trigger firing sequence can be set in Oracle 11g using FOLLOWS keyword
- Compound Triggers to encapsulate multiple triggering timings in one body
- Triggers can be created in ENABLED/DISABLED mode
- The DML triggers in 11g are 25% faster than their earlier versions in terms of compilation, execution and firing
In the tutorial, you will learn how Compound Triggers works and its benefits to the users.
1. Compound Triggers: Introduction
Compound triggers are the database DML triggers which ‘compounds’ or combines all the triggering timings under one trigger body. The triggering timings available for database triggers are BEFORE STATEMENT, AFTER STATEMENT, BEFORE EACH ROW, and AFTER EACH ROW. Trigger level variables can be defined which would be visible and accessible in all the timing blocks. Compound trigger body does not contain any exception section but recommends each block to contain its own exception handler under exception section.
2. Compound Triggers: Reaping the Benefits
Compound trigger resolves few big issues, which have been the nightmare for the developers in the past.
- Mutating table error ORA-04091
- Multithread behavior maintains persistent state of session variables till the statement execution finishes. These are defined in the declaration section of the compound trigger.
- Enhanced performance in bulk operations
- Supports encapsulation of multiple program units; thus enhances code interactivity
3. Compound Triggers: Usage Guidelines
As a new induction to the database family, there are some introductory guidelines which must be followed while working with compound triggers
- Compound trigger are meant only for DML operations. No support still for DDL and system operations.
- Exception handling process has to be done for each timing block.
- Compound trigger remains passive, if the DML does not changes any rows
- :OLD and :NEW variable identifiers can exist only in ROW level blocks, where :NEW values can only be modified in BEFORE EACH ROW block
- No support for WHEN clause. Earlier, WHEN clause could be utilized to impose condition on the trigger action.
- No support for PRAGMA_AUTONOMOUS_TRANSACTION
There are many debates over the above guidelines, which many identify as compound trigger restrictions. But from developer’s perspective, the benefits from compound triggers are much heavier than these restrictions. The assumptions and restrictions can be melted into code practice and as ‘usage guidelines’.
4. Compound Triggers: Syntax
The syntax shows the four timing blocks in a set order. Example Syntax [1a]: For DML Triggers
CREATE OR REPLACE TRIGGER [TRIGGER NAME] FOR [DML] ON [TABLE NAME] COMPOUND TRIGGER -- Initial section -- Declarations -- Subprograms Optional SECTION BEFORE STATEMENT IS …; Optional SECTION AFTER STATEMENT IS …; Optional SECTION BEFORE EACH ROW IS …; Optional SECTION AFTER EACH ROW IS …; END;
In the syntax, note that the compound trigger offers only the declaration section, which are again locally available in the timing blocks. Example Syntax [1b]: For database views INSTEAD OF EACH ROW IS …; END; Note that none of the timing blocks should be duplicated. Oracle server raises exception PLS-00676 if duplicate timing blocks is found in the compound trigger definition.
5. Compound Triggers: Additional Notes
5.1. USER_TRIGGERS dictionary view structure has been restructured to include the metadata for compound triggers. The new columns specify whether a timing block is available in the compound trigger body. 5.2. The new triggering event has been introduced for compound trigger as ‘COMPOUND’. The figure below shows the TRIGGER_TYPE, TRIGGERING_EVENT and TABLE_NAME of the trigger TRG_COMP_DEMO. 5.3. The columns BEFORE_STATEMENT, BEFORE_ROW, AFTER_ROW, and AFTER_STATEMENT are set as YES/NO based on the timing blocks available in the compound trigger. For example, the trigger TRG_COMP_DEMO contains all the timing blocks in the trigger body, the columns can be queried as below.
6. Applications of Compound Triggers
A compound trigger is best suited to achieve two objectives.
- Yield better performance while loading a table simultaneous to a running transaction and using its values.
- Resolve mutating table error (ORA-04091)
I will illustrate both the accomplishments in below steps
6.1. Demonstrating performance gains during simultaneous loading
1. Two tables were created
SQL>CREATE TABLE ORDERS (ORD_ID NUMBER, ITEM_CODE VARCHAR2(100), ORD_QTY NUMBER, ORD_DATE DATE); TABLE created. SQL>CREATE TABLE ORDER_ARCHIVE(ORD_ID NUMBER, ORD_CODE VARCHAR2(100)); TABLE created.
2. To demonstrate the performance difference between the versions, I create a normal FOR EACH ROW trigger on ORDERS table to insert data into ORDER_ARCHIVE table.
SQL>CREATE OR REPLACE TRIGGER TRG_ORDERS BEFORE INSERT ON ORDERS FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('Insert order‘||:NEW.ORD_ID||’ into ORDER_ARCHIVE'); INSERT INTO ORDER_ARCHIVE VALUES (:NEW.ORD_ID,:NEW.ITEM_CODE); END; / TRIGGER created.
3. Now, I will insert the test data into the ORDERS table using SELECT statement
SQL>INSERT INTO orders(ord_id, item_code, ord_date) 2 SELECT (rownum+1)*100, 'ITEM_'||rownum, sysdate-rownum 3 FROM DUAL 4* CONNECT BY ROWNUM < 15; INSERT ORDER 1 INTO ORER_ARCHIVE INSERT ORDER 2 INTO ORER_ARCHIVE INSERT ORDER 3 INTO ORER_ARCHIVE INSERT ORDER 4 INTO ORER_ARCHIVE INSERT ORDER 5 INTO ORER_ARCHIVE INSERT ORDER 6 INTO ORER_ARCHIVE INSERT ORDER 7 INTO ORER_ARCHIVE INSERT ORDER 8 INTO ORER_ARCHIVE INSERT ORDER 9 INTO ORER_ARCHIVE INSERT ORDER 10 INTO ORER_ARCHIVE INSERT ORDER 11 INTO ORER_ARCHIVE INSERT ORDER 12 INTO ORER_ARCHIVE INSERT ORDER 13 INTO ORER_ARCHIVE INSERT ORDER 14 INTO ORER_ARCHIVE 14 ROWS created.
Note the output of the above INSERT process. For each of the 14 records inserted into the ORDERS table through the trigger, Oracle server makes simultaneous inserts into the ORDER_ARCHIVE table. 4.
Compound triggers in Oracle 11g have the ability to perform simultaneous bulk insert process into the table. I will create the compound trigger TRG_COMP_ORDERS to implement the bulk loading process. Note the use of associative arrays, which hold the data to be inserted. It flushes off once the data is replicated into the ORDER_ARCHIVE table when the index count reaches 20 or the statement execution completes.
SQL>CREATE OR REPLACE TRIGGER TRG_COMP_SAL FOR INSERT ON ORDERS COMPOUND TRIGGER TYPE ORDER_T IS TABLE OF ORDER_ARCHIVE%ROWTYPE INDEX BY PLS_INTEGER; L_ORDERS ORDER_T; I NUMBER := 0; AFTER EACH ROW IS BEGIN I := I+1; L_ORDERS(I).ORD_ID := :NEW.ORD_ID; L_ORDERS(I).ORD_CODE := :NEW.ITEM_CODE; IF I >= 20 THEN DBMS_OUTPUT.PUT_LINE('Bulk Load for 20 orders'); FOR J IN 1..I LOOP INSERT INTO ORDER_ARCHIVE VALUES L_ORDERS(J); END LOOP; L_ORDERS.DELETE; I := 0; END IF; END AFTER EACH ROW; AFTER STATEMENT IS BEGIN DBMS_OUTPUT.PUT_LINE('Statement level loading'); FORALL J IN 1..L_ORDERS.COUNT INSERT INTO ORDER_ARCHIVE VALUES L_ORDERS(J); L_ORDERS.DELETE; I := 0; END AFTER STATEMENT; END; / TRIGGER created.
5. I will insert 64 rows (instead of just 14) into ORDERS table using SELECT query. The simultaneous inserts into ORDER_ARCHIVE table are achieved only in 3 bulk insert process.
SQL>INSERT INTO orders(ord_id, item_code, ord_date) 2 SELECT (rownum+1)*100, 'ITEM_'||rownum, sysdate-rownum 3 FROM DUAL 4 CONNECT BY rownum < 65 5 / Bulk LOAD FOR 20 orders Bulk LOAD FOR 20 orders Bulk LOAD FOR 20 orders Statement level loading 64 ROWS created.
Above results clearly differentiate the compound trigger from the conventional triggers in terms of performance while simultaneous data loading.
6.2. Demonstration of Mutating table solution using Compound triggers
Now, let us learn about the most accomplished achievement of Compound Triggers, i.e. their ability to tackle with Mutating table error (ORA-04091). Mutating table occurs when a table is referenced when it is in floating state. A table is in flux or floating state when it is the participant of a running transaction. Earlier, it used to be taken as a precaution during coding or its effects were diluted using workaround solutions. Few of the efficient workarounds are as below.
- Change in logic implementation and code design
- Using PRAGMA_AUTONOMOUS_TRANSACTION
- Conversion of row level trigger to statement level
- Defining package to hold variables at session level; these variables would hold the values and later would be inserted once the statement execution finishes.
How compound trigger fixes the problem?
Compound trigger contains the timing blocks and variables, which are persistent till the statement completes and are visible in all the timing blocks of the trigger. These variables are of collection type which holds the table data, before it enters the flux mode. Table gets locked once it moves to flux mode, but the backup data in the collection variables remains persistent and can be used for reference within the trigger. Therefore, the logic used is same as earlier, but being a single compiled unit, it is more convenient and easily maintained. In the example code below, the compound trigger fires on update of ORDERS table. It queries the ORDERS table to fetch and display the old quantity. For a conventional DML trigger, the situation is the best candidate for ORA-04091 Mutating table error. A user updates the order quantity for order id 600. Now before the UPDATE statement executes, the BEFORE STATEMENT block fetches the old ‘order quantity’ value (90) and stores in a collection type variable. Now when the statement executes, it updates the ‘order quantity’ to 150. AFTER STATEMENT block in the trigger displays the ‘old quantity’ value.
CREATE OR REPLACE TRIGGER TRG_ORDERS FOR UPDATE ON ORDERS COMPOUND TRIGGER TYPE ORD_QTY_T IS TABLE OF ORDERS.ORD_QTY%TYPE; L_ORD_QTY ORD_QTY_T; BEFORE STATEMENT IS BEGIN SELECT ORD_QTY BULK COLLECT INTO L_ORD_QTY FROM ORDERS WHERE ORD_ID=600; END BEFORE STATEMENT; AFTER EACH ROW IS BEGIN DBMS_OUTPUT.PUT_LINE('Old Quantity Value:'||L_ORD_QTY(L_ORD_QTY.FIRST)); END AFTER EACH ROW; END; / SQL>UPDATE ORDERS 2 SET ORD_QTY = 150 3 WHERE ORD_ID = 600; OLD Quantity VALUE:90 1 ROW updated.
Compound trigger combines the properties of both statement and row level triggers. The logic which was earlier maintained at row and statement level can now be placed into one single body, with state and persistency in data across the timings.