Oracle Statement-Level Trigger FAQs with Examples

A statement-level trigger fires once for the entire SQL statement, regardless of how many rows the statement affects.

The key difference from a row-level trigger is:

-- Statement-level
CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    ...
END;
/

There is no FOR EACH ROW.

1. What is a statement-level trigger?

A statement-level trigger executes once when a DML statement occurs.

Suppose:

UPDATE employees
SET salary = salary + 1000;

If this updates 500 rows:

Statement-level trigger → fires 1 time
Row-level trigger       → fires 500 times

Example

CREATE OR REPLACE TRIGGER trg_emp_update
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Employees table was updated');
END;
/

The trigger fires once for the entire UPDATE.

2. How do I identify a statement-level trigger?

A trigger is statement-level when it doesn't contain:

FOR EACH ROW

Statement-level

CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Insert statement executed');
END;
/

Row-level

CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('One row inserted');
END;
/

3. What happens if 1,000 rows are updated?

Consider:

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Trigger fired');
END;
/

Then:

UPDATE employees
SET salary = salary + 500;

If 1,000 rows are affected:

UPDATE statement
      |
      +---- 1,000 rows
      |
      ↓
Statement-level trigger
      |
      ↓
Fires ONE time

4. Can a statement-level trigger use :OLD and :NEW?

No.

This is one of the most common interview questions.

:OLD and :NEW are row-level correlation variables.

This is invalid for a statement-level trigger:

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE(:NEW.salary);
END;
/

You would get an error because there is no individual row associated with a statement-level trigger.

Use row-level trigger instead

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(:NEW.salary);
END;
/

5. What is the difference between statement-level and row-level triggers?

Feature Statement-level Row-level
FOR EACH ROW ❌ No ✅ Yes
Fires per statement
Fires per row
:OLD available
:NEW available
Good for auditing individual rows Less suitable Good
Good for statement-level processing Good Less suitable

Example:

UPDATE employees
SET department_id = 20
WHERE department_id = 10;

If 100 rows are affected:

Statement trigger → 1 execution
Row trigger       → 100 executions

6. Can a statement-level trigger handle INSERT, UPDATE and DELETE?

Yes.

CREATE OR REPLACE TRIGGER trg_emp_changes
AFTER INSERT OR UPDATE OR DELETE ON employees
BEGIN

    IF INSERTING THEN
        DBMS_OUTPUT.PUT_LINE('INSERT statement executed');

    ELSIF UPDATING THEN
        DBMS_OUTPUT.PUT_LINE('UPDATE statement executed');

    ELSIF DELETING THEN
        DBMS_OUTPUT.PUT_LINE('DELETE statement executed');
    END IF;

END;
/

Notice that:

INSERTING
UPDATING
DELETING

can be used even though :OLD and :NEW cannot.

7. Can we use UPDATE OF with a statement-level trigger?

Yes.

CREATE OR REPLACE TRIGGER trg_salary_update
AFTER UPDATE OF salary ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Salary update statement executed');
END;
/

This trigger fires when salary is included in the UPDATE statement.

For example:

UPDATE employees
SET salary = salary + 1000;

fires the trigger once.

But:

UPDATE employees
SET department_id = 20;

does not fire it.

Important interview point:

UPDATE OF salary doesn't mean Oracle checks whether the value actually changed.

For example:

UPDATE employees
SET salary = salary;

can still cause the trigger to fire.

8. Can a statement-level trigger contain WHEN?

No.

The WHEN clause is associated with row-level triggers, because it evaluates values such as NEW and OLD for individual rows.

For example:

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
WHEN (NEW.salary > 100000)
BEGIN
    ...
END;
/

This is row-level.

A statement-level trigger cannot use:

WHEN (NEW.salary > 100000)

because there is no individual NEW row.

9. Can a statement-level trigger query the same table?

Generally, yes.

This is an important difference from a row-level trigger.

Example:

CREATE OR REPLACE TRIGGER trg_emp_update
AFTER UPDATE ON employees
DECLARE
    v_count NUMBER;
BEGIN

    SELECT COUNT(*)
    INTO v_count
    FROM employees;

    DBMS_OUTPUT.PUT_LINE(
        'Employees = ' || v_count
    );

END;
/

A statement-level trigger isn't in the middle of processing an individual row, so the classic mutating-table problem associated with querying the triggering table from a row-level trigger does not apply in the same way.

10. What is the mutating-table difference?

This is a popular interview question.

Row-level trigger

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
DECLARE
    v_count NUMBER;
BEGIN

    SELECT COUNT(*)
    INTO v_count
    FROM employees;

END;
/

This can result in:

ORA-04091: table EMPLOYEES is mutating

Statement-level trigger

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
DECLARE
    v_count NUMBER;
BEGIN

    SELECT COUNT(*)
    INTO v_count
    FROM employees;

END;
/

This is generally allowed because the trigger executes at the statement level.

11. Can a statement-level trigger perform DML?

Yes.

For example, suppose you want to record that the EMPLOYEES table was modified.

CREATE TABLE employee_change_log (
    change_type VARCHAR2(20),
    changed_by  VARCHAR2(100),
    changed_on  TIMESTAMP
);

Trigger:

CREATE OR REPLACE TRIGGER trg_emp_change
AFTER INSERT OR UPDATE OR DELETE ON employees
BEGIN

    INSERT INTO employee_change_log
    VALUES (
        CASE
            WHEN INSERTING THEN 'INSERT'
            WHEN UPDATING  THEN 'UPDATE'
            WHEN DELETING  THEN 'DELETE'
        END,
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        SYSTIMESTAMP
    );

END;
/

If an update changes 500 rows, only one audit row is inserted.

That's exactly where a statement-level trigger can be useful.

12. Can a statement-level trigger modify :NEW?

No.

There is no :NEW in a statement-level trigger.

If you want to change a value before it is stored, use a row-level BEFORE trigger.

CREATE OR REPLACE TRIGGER trg_emp_upper
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.first_name := UPPER(:NEW.first_name);
END;
/

13. Can a statement-level trigger validate data?

Yes, but it is suited to statement-level validation, rather than individual-row validation.

For example, you might check a table-wide condition after an operation:

CREATE OR REPLACE TRIGGER trg_emp_check
AFTER UPDATE ON employees
DECLARE
    v_count NUMBER;
BEGIN

    SELECT COUNT(*)
    INTO v_count
    FROM employees
    WHERE salary < 0;

    IF v_count > 0 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Negative salary found'
        );
    END IF;

END;
/

For checking each incoming row individually, a row-level trigger is usually more appropriate.

14. Can a statement-level trigger fire for a statement that affects zero rows?

Yes.

This is a very important interview point.

Suppose:

UPDATE employees
SET salary = salary + 1000
WHERE employee_id = -9999;

If no rows match:

Rows affected = 0

A statement-level trigger associated with UPDATE can still fire because the UPDATE statement was executed.

A row-level trigger does not fire because there were no rows to process.

Remember

Statement-level:
SQL statement executed → trigger fires

Row-level:
Row affected → trigger fires

15. What happens with DELETE affecting zero rows?

Same principle.

DELETE FROM employees
WHERE employee_id = -9999;

If no rows are deleted:

Statement-level trigger → can fire
Row-level trigger       → doesn't fire

16. Can a statement-level trigger commit?

No.

This is invalid:

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    COMMIT;
END;
/

It results in an error such as:

ORA-04092: cannot COMMIT in a trigger

A trigger normally participates in the same transaction as the DML that fired it.

17. Can a statement-level trigger call a procedure?

Yes.

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    log_employee_update;
END;
/

Procedure:

CREATE OR REPLACE PROCEDURE log_employee_update
IS
BEGIN
    INSERT INTO employee_change_log
    VALUES (
        'UPDATE',
        USER,
        SYSTIMESTAMP
    );
END;
/

This can be useful when the trigger itself should remain small.

18. What is the firing order?

For a simple DML operation, conceptually:

BEFORE STATEMENT
       ↓
BEFORE EACH ROW
       ↓
AFTER EACH ROW
       ↓
AFTER STATEMENT

For example, with an update:

UPDATE employees
SET salary = salary + 1000;

Oracle can execute triggers in this general sequence:

BEFORE UPDATE statement trigger
          ↓
BEFORE UPDATE row trigger × N rows
          ↓
AFTER UPDATE row trigger × N rows
          ↓
AFTER UPDATE statement trigger

This is an extremely useful concept for interviews.

19. Example with all four trigger timings

Suppose an update affects 3 employees.

CREATE OR REPLACE TRIGGER trg_before_stmt
BEFORE UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('BEFORE STATEMENT');
END;
/

CREATE OR REPLACE TRIGGER trg_before_row
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('BEFORE ROW');
END;
/

CREATE OR REPLACE TRIGGER trg_after_row
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('AFTER ROW');
END;
/

CREATE OR REPLACE TRIGGER trg_after_stmt
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('AFTER STATEMENT');
END;
/

For:

UPDATE employees
SET salary = salary + 1000;

Output conceptually:

BEFORE STATEMENT

BEFORE ROW
AFTER ROW

BEFORE ROW
AFTER ROW

BEFORE ROW
AFTER ROW

AFTER STATEMENT

So:

Statement triggers → once
Row triggers       → once per affected row

20. What is a good real-world use case?

A very good use case is maintaining a statement-level change log.

CREATE TABLE employee_statement_log (
    operation      VARCHAR2(20),
    username       VARCHAR2(100),
    operation_time TIMESTAMP
);

Trigger:

CREATE OR REPLACE TRIGGER trg_emp_statement_log
AFTER INSERT OR UPDATE OR DELETE ON employees
BEGIN

    INSERT INTO employee_statement_log
    (
        operation,
        username,
        operation_time
    )
    VALUES
    (
        CASE
            WHEN INSERTING THEN 'INSERT'
            WHEN UPDATING  THEN 'UPDATE'
            WHEN DELETING  THEN 'DELETE'
        END,
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        SYSTIMESTAMP
    );

END;
/

Then:

UPDATE employees
SET salary = salary + 1000;

If 2,000 employees are updated:

2,000 rows changed
       ↓
1 UPDATE statement
       ↓
1 statement-level trigger execution
       ↓
1 log record

21. What are common statement-level trigger interview traps?

Trap 1

Question: Does a statement-level trigger fire once per row?

Answer: ❌ No.

It fires once per SQL statement.

Trap 2

Question: Can statement-level triggers use :OLD and :NEW?

Answer: ❌ No.

Those are row-level correlation variables.

Trap 3

Question: Does a statement-level trigger fire if zero rows are affected?

Answer: ✅ Yes, the trigger can fire because the DML statement was executed.

Trap 4

Question: Does a row-level trigger fire if zero rows are affected?

Answer: ❌ No.

There are no rows for the trigger to process.

Trap 5

Question: Can a statement-level trigger query its triggering table?

Answer: ✅ Yes, generally. The classic mutating-table restriction applies to row-level access to the table being modified.

Trap 6

Question: Can a statement-level trigger modify :NEW?

Answer: ❌ No.

There is no :NEW in a statement-level trigger.

22. Statement-level vs row-level: interview summary

Memorize this:

Feature Statement Level Row Level
FOR EACH ROW No Yes
Firing Once / statement Once / row
:OLD No Yes
:NEW No Yes
WHEN No Yes
Modify :NEW No Yes
(BEFORE trigger)
Zero rows Can fire Doesn't fire
Mutating table Generally okay Can be a problem
The simplest way to remember

Statement-level = "Something happened to the table."

Row-level = "This particular row changed."

For example:

UPDATE employees
SET salary = salary * 1.10;

If 10,000 rows are affected:

Statement trigger:  1 time
Row trigger:       10,000 times

Interview Preparation:

The key concept to understand is the difference between statement-level and row-level triggers. Once this is clear, the next important topics are compound triggers, mutating-table errors, trigger firing order, autonomous transactions, recursive triggers, and trigger execution behavior.
```

Oracle Row Level Trigger Faqs With Examples

1. What is a row-level trigger?

A row-level trigger executes once for each row affected by an INSERT, UPDATE, or DELETE.

It is created using:

FOR EACH ROW

Example

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO employee_audit (
        employee_id,
        old_salary,
        new_salary,
        changed_on
    )
    VALUES (
        :OLD.employee_id,
        :OLD.salary,
        :NEW.salary,
        SYSDATE
    );
END;
/

If an UPDATE changes 10 employees, this trigger fires 10 times.

Frequently Asked Questions

2. What is the difference between statement-level and row-level triggers?

Feature Statement-level Row-level
FOR EACH ROW No Yes
Fires Once per SQL statement Once per affected row
:OLD, :NEW Not available Available
Example Update 100 rows → 1 firing Update 100 rows → 100 firings

Statement-level

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Update statement executed');
END;
/

Row-level

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('One employee updated');
END;
/

3. What are :OLD and :NEW?

They represent the values of a row before and after the DML operation.

INSERT  → :NEW available
UPDATE  → :OLD and :NEW available
DELETE  → :OLD available

Example:

CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Old Salary = ' || :OLD.salary
    );

    DBMS_OUTPUT.PUT_LINE(
        'New Salary = ' || :NEW.salary
    );
END;
/

4. Can we modify :NEW?

Yes, in a BEFORE row-level trigger.

This is commonly used for defaulting or transforming values.

CREATE OR REPLACE TRIGGER trg_emp_upper
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
    :NEW.first_name := UPPER(:NEW.first_name);
    :NEW.last_name  := UPPER(:NEW.last_name);
END;
/

If you execute:

INSERT INTO employees
       (employee_id, first_name, last_name)
VALUES (101, 'john', 'smith');

The stored values become:

JOHN
SMITH

Important:

You cannot modify :OLD.

:OLD.salary := 50000;   -- ❌ Invalid

5. Can we use :NEW in a DELETE trigger?

No.

For DELETE:

:OLD → available
:NEW → not available

Example:

CREATE OR REPLACE TRIGGER trg_emp_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Deleting employee: ' || :OLD.employee_id
    );
END;
/

6. Can we use :OLD during INSERT?

No.

For INSERT:

:NEW → available
:OLD → not available

Example:

CREATE OR REPLACE TRIGGER trg_emp_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'New employee: ' || :NEW.employee_id
    );
END;
/

7. What are BEFORE and AFTER row-level triggers?

BEFORE trigger

Runs before the row is inserted/updated/deleted.

Useful for:

  • Validation
  • Modifying :NEW
  • Setting default values
CREATE OR REPLACE TRIGGER trg_validate_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < 0 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Salary cannot be negative'
        );
    END IF;
END;
/

AFTER trigger

Runs after the row operation.

Useful for:

  • Auditing
  • Logging
  • Recording changes
CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    INSERT INTO salary_audit
    VALUES (
        :OLD.employee_id,
        :OLD.salary,
        :NEW.salary,
        SYSDATE
    );
END;
/

8. What does UPDATE OF salary mean?

It means the trigger fires only when the salary column is included in the UPDATE statement.

CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('Salary update detected');
END;
/

This fires for:

UPDATE employees
SET salary = 60000
WHERE employee_id = 101;

But it doesn't fire for:

UPDATE employees
SET department_id = 20
WHERE employee_id = 101;
Interview trap:

UPDATE OF salary means the column is referenced in the UPDATE statement. It doesn't necessarily mean the salary value actually changed.

For example:

UPDATE employees
SET salary = salary
WHERE employee_id = 101;

The trigger can still fire.

9. How do I check whether a value actually changed?

Use :OLD and :NEW.

CREATE OR REPLACE TRIGGER trg_salary_change
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    IF :OLD.salary != :NEW.salary THEN
        DBMS_OUTPUT.PUT_LINE('Salary actually changed');
    END IF;
END;
/

But there's a NULL issue with !=.

A safer comparison is:

IF (:OLD.salary != :NEW.salary)
   OR (:OLD.salary IS NULL AND :NEW.salary IS NOT NULL)
   OR (:OLD.salary IS NOT NULL AND :NEW.salary IS NULL)
THEN
    DBMS_OUTPUT.PUT_LINE('Salary changed');
END IF;

10. How do I prevent an employee's salary from decreasing?

A row-level BEFORE UPDATE trigger is a common example.

CREATE OR REPLACE TRIGGER trg_no_salary_decrease
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < :OLD.salary THEN
        RAISE_APPLICATION_ERROR(
            -20002,
            'Salary cannot be decreased'
        );
    END IF;
END;
/

Now:

UPDATE employees
SET salary = 40000
WHERE employee_id = 101;

will fail if the old salary was 50000.

11. How do I automatically set created_date?

CREATE OR REPLACE TRIGGER trg_emp_created
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.created_date := SYSDATE;
END;
/

Now:

INSERT INTO employees(employee_id, first_name)
VALUES (101, 'John');

automatically gets the current date.

Better modern alternative

If you're designing a new table, a column DEFAULT is often preferable:

created_date DATE DEFAULT SYSDATE

Use triggers when the logic genuinely requires procedural behavior.

12. How do I maintain updated_date?

CREATE OR REPLACE TRIGGER trg_emp_updated
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
    :NEW.updated_date := SYSDATE;
END;
/

Every updated row gets a new timestamp.

13. Can a trigger call a procedure?

Yes.

CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    audit_employee(:NEW.employee_id);
END;
/

Where:

CREATE OR REPLACE PROCEDURE audit_employee(
    p_employee_id NUMBER
)
IS
BEGIN
    INSERT INTO employee_log(employee_id, log_date)
    VALUES (p_employee_id, SYSDATE);
END;
/

This is useful when trigger logic becomes large, although you should avoid putting excessive business logic into triggers.

14. What is a mutating-table error?

One of the most important row-trigger interview questions.

Suppose you have:

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
DECLARE
    v_count NUMBER;
BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM employees;
END;
/

This can cause:

ORA-04091: table EMPLOYEES is mutating,
trigger/function may not see it

Why?

A row-level trigger is executing while Oracle is modifying the EMPLOYEES table.

Trying to query the same table from that row-level trigger can cause the mutating-table problem.

15. How do we solve a mutating-table problem?

Modern Oracle versions provide several approaches, including compound triggers.

Example:

CREATE OR REPLACE TRIGGER trg_emp
FOR UPDATE ON employees
COMPOUND TRIGGER

    AFTER EACH ROW IS
    BEGIN
        -- collect row information
        NULL;
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN
        -- safely query EMPLOYEES here
        NULL;
    END AFTER STATEMENT;

END;
/

The important idea is:

Row section
    ↓
collect information

Statement section
    ↓
query/process the table

This separates row-level collection from statement-level processing.

16. What happens if one row fails in a row-level trigger?

Suppose:

UPDATE employees
SET salary = salary + 1000;

affects 100 rows.

If your trigger raises an exception for one of those rows and the exception propagates, the SQL statement fails, and Oracle normally rolls back the statement's changes.

Example:

CREATE OR REPLACE TRIGGER trg_test
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary > 100000 THEN
        RAISE_APPLICATION_ERROR(
            -20003,
            'Salary too high'
        );
    END IF;
END;
/

If the update tries to create an invalid row, the statement fails rather than simply skipping that row.

17. Can a trigger commit?

Normally, no.

You cannot do:

CREATE OR REPLACE TRIGGER trg_test
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    COMMIT;  -- ❌ ORA-04092
END;
/

A trigger executes as part of the transaction that caused it.

18. Can a trigger perform DML on another table?

Yes.

For example:

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO employee_deleted_log
    (
        employee_id,
        deleted_date
    )
    VALUES
    (
        :OLD.employee_id,
        SYSDATE
    );
END;
/

This is a common auditing pattern.

19. Can a row-level trigger be used for auditing?

Yes.

Example:

CREATE TABLE emp_audit (
    employee_id NUMBER,
    old_salary   NUMBER,
    new_salary   NUMBER,
    changed_by   VARCHAR2(100),
    changed_on   DATE
);

Trigger:

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    INSERT INTO emp_audit
    (
        employee_id,
        old_salary,
        new_salary,
        changed_by,
        changed_on
    )
    VALUES
    (
        :OLD.employee_id,
        :OLD.salary,
        :NEW.salary,
        USER,
        SYSDATE
    );
END;
/

20. What is the difference between :NEW and NEW?

Inside PL/SQL trigger code, you normally use:

:NEW.column_name
:OLD.column_name

The colon indicates a correlation variable.

Example:

IF :NEW.salary > 100000 THEN
    ...
END IF;

21. Can we use WHEN with a row-level trigger?

Yes.

For example:

CREATE OR REPLACE TRIGGER trg_high_salary
AFTER UPDATE ON employees
FOR EACH ROW
WHEN (NEW.salary > 100000)
BEGIN
    INSERT INTO high_salary_log(employee_id)
    VALUES (:NEW.employee_id);
END;
/

A useful distinction:

Inside the WHEN clause, you generally reference the correlation name without the colon:

WHEN (NEW.salary > 100000)

Inside PL/SQL:

:NEW.salary

22. What are the valid timing/event combinations?

Common combinations are:

BEFORE INSERT
BEFORE UPDATE
BEFORE DELETE

AFTER INSERT
AFTER UPDATE
AFTER DELETE

You can combine events:

BEFORE INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW

You can also restrict an update trigger:

BEFORE UPDATE OF salary, department_id ON employees
FOR EACH ROW

23. Can one trigger handle INSERT, UPDATE and DELETE?

Yes.

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
BEGIN

    IF INSERTING THEN
        DBMS_OUTPUT.PUT_LINE('Inserted');
    ELSIF UPDATING THEN
        DBMS_OUTPUT.PUT_LINE('Updated');
    ELSIF DELETING THEN
        DBMS_OUTPUT.PUT_LINE('Deleted');
    END IF;

END;
/

Oracle provides:

INSERTING
UPDATING
DELETING

These are extremely useful in multi-event triggers.

24. What is a good interview example?

Requirement

Whenever an employee's salary changes, record the old salary, new salary, employee ID, user and timestamp.

Solution

CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN

    IF (:OLD.salary != :NEW.salary)
       OR (:OLD.salary IS NULL AND :NEW.salary IS NOT NULL)
       OR (:OLD.salary IS NOT NULL AND :NEW.salary IS NULL)
    THEN

        INSERT INTO salary_audit
        (
            employee_id,
            old_salary,
            new_salary,
            changed_by,
            changed_on
        )
        VALUES
        (
            :OLD.employee_id,
            :OLD.salary,
            :NEW.salary,
            SYS_CONTEXT('USERENV', 'SESSION_USER'),
            SYSTIMESTAMP
        );

    END IF;

END;
/

This demonstrates several important concepts:

  • Row-level trigger
  • AFTER UPDATE
  • UPDATE OF
  • :OLD
  • :NEW
  • NULL-safe comparison
  • Audit logging
  • Current database user
  • Timestamp

⭐ Quick interview cheat sheet

Remember this table:

Operation :OLD :NEW
INSERT
UPDATE
DELETE
FOR EACH ROW
     ↓
Row-level trigger

No FOR EACH ROW
     ↓
Statement-level trigger

BEFORE → validate/modify :NEW

AFTER → audit/log after the operation

:OLD → previous value

:NEW → new value

INSERTING → INSERT event

UPDATING → UPDATE event

DELETING → DELETE event

Mutating table → avoid querying the table currently being changed from its row-level trigger; consider a compound trigger.
If you're preparing for an Oracle interview, the next-level questions are usually around mutating-table errors, compound triggers, autonomous transactions, trigger firing order, recursive triggers, and :OLD/:NEW edge cases.

Oracle DML Triggers Faqs With Examples

A DML trigger automatically executes when INSERT, UPDATE, or DELETE occurs on a table or view.

DML triggers are very common in Oracle interviews, especially questions around :OLD, :NEW, row-level vs statement-level, BEFORE vs AFTER, mutating-table errors, and compound triggers.

1. What is a DML trigger?

A DML trigger fires automatically when a DML operation occurs.

DML operations are:

INSERT
UPDATE
DELETE

Example:

CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('Employee inserted');
END;
/

Whenever:

INSERT INTO employees(employee_id, employee_name)
VALUES (101, 'John');

executes, the trigger fires.

2. What is the basic syntax?

Row-level trigger

CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH ROW
BEGIN
    -- PL/SQL code
END;
/

Statement-level trigger

CREATE OR REPLACE TRIGGER trigger_name
AFTER INSERT OR UPDATE OR DELETE
ON table_name
BEGIN
    -- PL/SQL code
END;
/

The key difference is:

FOR EACH ROW

3. What is a row-level trigger?

A row-level trigger fires once for every row affected by the DML statement.

Example:

CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Employee: ' || :NEW.employee_id
    );
END;
/

Suppose:

UPDATE employees
SET salary = salary * 1.10;

If 500 employees are updated, the trigger fires 500 times.

4. What is a statement-level trigger?

A statement-level trigger fires once for the entire SQL statement.

CREATE OR REPLACE TRIGGER trg_emp_update
AFTER UPDATE ON employees
BEGIN
    DBMS_OUTPUT.PUT_LINE('Employees table was updated');
END;
/

If:

UPDATE employees
SET salary = salary * 1.10;

updates 500 rows, the trigger fires only once.

5. Row-level vs statement-level trigger

Feature Row-level Statement-level
FOR EACH ROW Yes No
Fires Once per row Once per statement
:OLD / :NEW Available Not available
Example Audit individual employee Log that an UPDATE occurred
500 rows updated 500 executions 1 execution
Interview shortcut:

FOR EACH ROW = row-level trigger.

6. What are :OLD and :NEW?

They are correlation variables used in row-level DML triggers.

DML :OLD :NEW
INSERT
UPDATE
DELETE

Example:

CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Old: ' || :OLD.salary ||
        ' New: ' || :NEW.salary
    );
END;
/

7. What is :NEW used for?

:NEW represents the new value of a column.

For example:

CREATE OR REPLACE TRIGGER trg_upper_name
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.employee_name := UPPER(:NEW.employee_name);
END;
/

Now:

INSERT INTO employees(employee_id, employee_name)
VALUES (101, 'john');

stores:

JOHN

instead of:

john

8. Can we modify :NEW?

Yes, in a BEFORE row-level trigger.

Example:

CREATE OR REPLACE TRIGGER trg_default_salary
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary IS NULL THEN
        :NEW.salary := 30000;
    END IF;
END;
/

If the user inserts:

INSERT INTO employees(employee_id, employee_name)
VALUES (101, 'John');

the trigger assigns:

salary = 30000

9. Can we modify :OLD?

No.

:OLD represents the value that already exists.

For example, this is invalid:

:OLD.salary := 50000;

You cannot change the old value.

10. Can we modify :NEW in an AFTER trigger?

Generally, no.

If you need to change the incoming value, use a:

BEFORE INSERT
BEFORE UPDATE

row-level trigger.

Example:

CREATE OR REPLACE TRIGGER trg_salary
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
    :NEW.salary := ROUND(:NEW.salary);
END;
/

11. What is a BEFORE trigger?

A BEFORE trigger executes before the DML operation.

Example:

CREATE OR REPLACE TRIGGER trg_validate_salary
BEFORE INSERT OR UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < 0 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Salary cannot be negative'
        );
    END IF;
END;
/

This is useful for:

  • Validation
  • Modifying :NEW
  • Setting default values

12. What is an AFTER trigger?

An AFTER trigger executes after the DML operation.

Example:

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO employee_audit
    (
        employee_id,
        old_salary,
        new_salary,
        changed_on
    )
    VALUES
    (
        :OLD.employee_id,
        :OLD.salary,
        :NEW.salary,
        SYSDATE
    );
END;
/

This is commonly used for:

  • Auditing
  • Logging
  • Maintaining related information

13. BEFORE vs AFTER trigger

BEFORE AFTER
Executes before DML Executes after DML
Can modify :NEW Cannot modify :NEW
Good for validation Good for auditing
Good for assigning values Good for logging successful changes
Easy way to remember:

BEFORE → Check / Change
AFTER → Record / React

14. Can one trigger handle INSERT, UPDATE and DELETE?

Yes.

CREATE OR REPLACE TRIGGER trg_emp_changes
AFTER INSERT OR UPDATE OR DELETE
ON employees
FOR EACH ROW
BEGIN
    IF INSERTING THEN
        DBMS_OUTPUT.PUT_LINE('INSERT occurred');

    ELSIF UPDATING THEN
        DBMS_OUTPUT.PUT_LINE('UPDATE occurred');

    ELSIF DELETING THEN
        DBMS_OUTPUT.PUT_LINE('DELETE occurred');
    END IF;
END;
/

Oracle provides:

INSERTING
UPDATING
DELETING

to determine which operation caused the trigger.

15. What are INSERTING, UPDATING, and DELETING?

They are conditional predicates used inside DML triggers.

Example:

IF INSERTING THEN
    ...
ELSIF UPDATING THEN
    ...
ELSIF DELETING THEN
    ...
END IF;

You can also check a particular column:

IF UPDATING('SALARY') THEN
    ...
END IF;

16. How do I create an audit trigger?

Suppose:

CREATE TABLE employee_audit (
    employee_id NUMBER,
    old_salary  NUMBER,
    new_salary  NUMBER,
    action_type VARCHAR2(20),
    changed_by  VARCHAR2(100),
    changed_on  DATE
);

Trigger:

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE
ON employees
FOR EACH ROW
BEGIN

    IF INSERTING THEN

        INSERT INTO employee_audit
        VALUES (
            :NEW.employee_id,
            NULL,
            :NEW.salary,
            'INSERT',
            USER,
            SYSDATE
        );

    ELSIF UPDATING THEN

        INSERT INTO employee_audit
        VALUES (
            :NEW.employee_id,
            :OLD.salary,
            :NEW.salary,
            'UPDATE',
            USER,
            SYSDATE
        );

    ELSIF DELETING THEN

        INSERT INTO employee_audit
        VALUES (
            :OLD.employee_id,
            :OLD.salary,
            NULL,
            'DELETE',
            USER,
            SYSDATE
        );

    END IF;

END;
/

This is a very common interview example.

17. How do I prevent deleting an employee?

CREATE OR REPLACE TRIGGER trg_no_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
    RAISE_APPLICATION_ERROR(
        -20001,
        'Employee deletion is not allowed'
    );
END;
/

Now:

DELETE FROM employees
WHERE employee_id = 101;

fails.

18. How do I prevent salary reduction?

CREATE OR REPLACE TRIGGER trg_no_salary_reduction
BEFORE UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < :OLD.salary THEN
        RAISE_APPLICATION_ERROR(
            -20002,
            'Salary cannot be reduced'
        );
    END IF;
END;
/

Example:

UPDATE employees
SET salary = 40000
WHERE employee_id = 101;

If the old salary was 50000, the trigger raises an error.

19. How do I automatically update modified_date?

CREATE OR REPLACE TRIGGER trg_modified_date
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
    :NEW.modified_date := SYSDATE;
END;
/

Every update automatically changes the modification timestamp.

20. How do I automatically set created_date?

CREATE OR REPLACE TRIGGER trg_created_date
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.created_date := SYSDATE;
END;
/

However, for a simple default value, a column definition is often preferable:

created_date DATE DEFAULT SYSDATE

21. What is a conditional DML trigger?

A trigger that handles different logic depending on the DML operation.

Example:

CREATE OR REPLACE TRIGGER trg_emp
AFTER INSERT OR UPDATE OR DELETE
ON employees
FOR EACH ROW
BEGIN
    IF INSERTING THEN
        -- INSERT logic

    ELSIF UPDATING THEN
        -- UPDATE logic

    ELSIF DELETING THEN
        -- DELETE logic
    END IF;
END;
/

22. Can I create a trigger for a specific column?

Yes, particularly for UPDATE.

CREATE OR REPLACE TRIGGER trg_salary_update
BEFORE UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE('Salary is being updated');
END;
/

This trigger fires for an UPDATE statement involving the salary column.

You can also use:

IF UPDATING('SALARY') THEN
    ...
END IF;

23. What is the difference between UPDATE OF salary and UPDATING('salary')?

UPDATE OF salary

Specified in the trigger definition:

BEFORE UPDATE OF salary ON employees

It limits the trigger event to updates involving that column.

UPDATING('SALARY')

Used inside a trigger:

IF UPDATING('SALARY') THEN
    ...
END IF;

It lets you conditionally execute code based on the column.

24. What is a mutating-table error?

One of the most important Oracle trigger interview questions.

Suppose:

CREATE OR REPLACE TRIGGER trg_emp
BEFORE UPDATE ON employees
FOR EACH ROW
DECLARE
    v_count NUMBER;
BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM employees;
END;
/

When employees is being modified row-by-row, the trigger attempts to query the same table.

Oracle may raise:

ORA-04091: table EMPLOYEES is mutating

This is called a mutating-table error.

25. Why does a mutating-table error occur?

During a row-level trigger, Oracle is in the middle of modifying the table.

The table is considered to be in a changing state.

Trying to query that same table from the row-level trigger can produce inconsistent results, so Oracle prevents the operation.

Interview answer:

A mutating-table error occurs when a row-level trigger tries to query or modify the table that caused the trigger to fire.

26. How can we solve a mutating-table problem?

Common solutions include:

  • Compound trigger
  • Statement-level processing
  • Package variables with multiple trigger sections — older approach
  • Redesigning the logic
  • Moving business logic outside the trigger where appropriate

A compound trigger is generally the modern approach.

27. What is a compound trigger?

A compound trigger allows multiple timing sections in a single trigger.

Example structure:

CREATE OR REPLACE TRIGGER trg_emp
FOR INSERT OR UPDATE OR DELETE ON employees
COMPOUND TRIGGER

    BEFORE STATEMENT IS
    BEGIN
        NULL;
    END BEFORE STATEMENT;

    AFTER EACH ROW IS
    BEGIN
        NULL;
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN
        NULL;
    END AFTER STATEMENT;

END;
/

It can maintain state between the different timing sections.

28. What is the difference between row-level and compound triggers?

A compound trigger is not simply another "level" of trigger.

Think of it as a trigger that can contain multiple timing points:

BEFORE STATEMENT
       ↓
BEFORE EACH ROW
       ↓
AFTER EACH ROW
       ↓
AFTER STATEMENT

This makes it especially useful for complex DML processing and mutating-table scenarios.

29. Can a DML trigger contain COMMIT?

Normally, no.

This is invalid:

CREATE OR REPLACE TRIGGER trg_test
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    INSERT INTO employee_audit VALUES (...);

    COMMIT;
END;
/

Oracle can raise:

ORA-04092: cannot COMMIT in a trigger

The trigger is part of the transaction that caused it to execute.

30. Can a trigger call a procedure?

Yes.

CREATE OR REPLACE TRIGGER trg_employee
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
    send_employee_notification(:NEW.employee_id);
END;
/

For complex logic, keeping reusable code in a procedure/package can make the trigger easier to maintain.

31. What happens if the trigger fails?

Suppose:

CREATE OR REPLACE TRIGGER trg_test
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    RAISE_APPLICATION_ERROR(
        -20001,
        'Insert not allowed'
    );
END;
/

Then:

INSERT INTO employees VALUES (...);

fails.

The DML operation does not successfully complete.

This is why triggers can be used for validation and enforcement.

32. Can a trigger cause another trigger to fire?

Yes.

For example:

UPDATE EMPLOYEES
      ↓
Trigger A
      ↓
UPDATE DEPARTMENTS
      ↓
Trigger B

This is called cascading trigger execution.

Too many cascading triggers can make an application difficult to debug.

33. How do I disable a trigger?

Disable:

ALTER TRIGGER trg_emp DISABLE;

Enable:

ALTER TRIGGER trg_emp ENABLE;

For all triggers on a table:

ALTER TABLE employees DISABLE ALL TRIGGERS;

and:

ALTER TABLE employees ENABLE ALL TRIGGERS;

34. How do I find triggers on a table?

SELECT trigger_name,
       triggering_event,
       trigger_type,
       status
FROM user_triggers
WHERE table_name = 'EMPLOYEES';

Useful columns include:

TRIGGER_NAME
TRIGGER_TYPE
TRIGGERING_EVENT
TABLE_NAME
STATUS

35. How do I drop a trigger?

DROP TRIGGER trg_emp;

36. DML trigger vs constraint

Suppose you want:

Salary should never be negative.

A trigger can do this:

CREATE OR REPLACE TRIGGER trg_salary
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < 0 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Invalid salary'
        );
    END IF;
END;
/

But a constraint is simpler:

ALTER TABLE employees
ADD CONSTRAINT chk_salary
CHECK (salary >= 0);
Interview rule:

If a requirement can be handled cleanly with a constraint, prefer the constraint over a trigger.

37. When should we use DML triggers?

Good use cases include:

  • Auditing changes
  • Maintaining history tables
  • Validating complex business rules
  • Automatically setting or modifying values
  • Recording who changed data and when
  • Maintaining related information
  • Enforcing rules that cannot be handled cleanly by constraints

⭐ Most Important DML Trigger Interview Questions

Q1. What is a DML trigger?

A trigger that automatically fires when INSERT, UPDATE, or DELETE occurs.

Q2. What is the difference between row-level and statement-level triggers?

FOR EACH ROW
      ↓
Row-level trigger
      ↓
Fires once for every affected row

No FOR EACH ROW
      ↓
Statement-level trigger
      ↓
Fires once for the entire statement

Q3. What are :OLD and :NEW?

They are correlation variables available in row-level DML triggers.

INSERT → :NEW
UPDATE → :OLD and :NEW
DELETE → :OLD

Q4. Can we modify :NEW?

Yes, in a BEFORE row-level trigger.

Q5. Can we modify :OLD?

No. :OLD represents the existing value.

Q6. What is a mutating-table error?

It occurs when a row-level trigger tries to query or modify the same table that caused the trigger to fire.

ORA-04091: table EMPLOYEES is mutating

Q7. How can you solve a mutating-table problem?

Common solutions include:

  • Compound trigger
  • Statement-level processing
  • Package variables
  • Redesigning the logic

Q8. Can a trigger contain COMMIT?

No. Normal transaction control such as COMMIT is not allowed inside a trigger.

Q9. What is the difference between BEFORE and AFTER?

BEFORE → Validate / Change
AFTER  → Audit / Record / React

Q10. What are INSERTING, UPDATING and DELETING?

They are conditional predicates used to determine which DML operation caused the trigger.

IF INSERTING THEN
    ...
ELSIF UPDATING THEN
    ...
ELSIF DELETING THEN
    ...
END IF;

🔥 One Interview Scenario Worth Practicing

Requirement:

Every salary change must be audited, salary should never be reduced, and the employee's modified_date should automatically update whenever the employee record changes.

You could implement salary validation with:

CREATE OR REPLACE TRIGGER trg_no_salary_reduction
BEFORE UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < :OLD.salary THEN
        RAISE_APPLICATION_ERROR(
            -20002,
            'Salary cannot be reduced'
        );
    END IF;
END;
/

And separately implement an audit trigger:

CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE
ON employees
FOR EACH ROW
BEGIN

    IF INSERTING THEN

        INSERT INTO employee_audit
        VALUES (
            :NEW.employee_id,
            NULL,
            :NEW.salary,
            'INSERT',
            USER,
            SYSDATE
        );

    ELSIF UPDATING THEN

        INSERT INTO employee_audit
        VALUES (
            :NEW.employee_id,
            :OLD.salary,
            :NEW.salary,
            'UPDATE',
            USER,
            SYSDATE
        );

    ELSIF DELETING THEN

        INSERT INTO employee_audit
        VALUES (
            :OLD.employee_id,
            :OLD.salary,
            NULL,
            'DELETE',
            USER,
            SYSDATE
        );

    END IF;

END;
/
Key Interview Takeaway:

A DML trigger automatically fires for INSERT, UPDATE, or DELETE. Row-level triggers use FOR EACH ROW and provide :OLD and :NEW. BEFORE triggers are commonly used for validation and changing :NEW, while AFTER triggers are commonly used for auditing and logging. Always remember the mutating-table problem and the role of compound triggers in solving complex row-level processing.

Oracle DDL Triggers Faqs With Examples

1. What is a DDL trigger?

A DDL trigger executes when a DDL statement is issued.

For example:

CREATE TABLE employees (
    emp_id NUMBER,
    emp_name VARCHAR2(100)
);

A DDL trigger can automatically record that the table was created.

2. What are common DDL events?

Common events include:

CREATE
ALTER
DROP
TRUNCATE
RENAME

You can also use the broader event category:

DDL

This can cover multiple DDL operations.

3. What is the basic syntax?

Schema-level DDL trigger:

CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
    -- trigger logic
END;
/

This trigger fires when the specified DDL operations occur in the schema.

4. What is a schema-level trigger?

A schema-level trigger belongs to a particular user's schema.

CREATE OR REPLACE TRIGGER trg_schema_ddl
AFTER CREATE OR DROP
ON SCHEMA
BEGIN
    DBMS_OUTPUT.PUT_LINE('DDL operation occurred');
END;
/

For example:

CREATE TABLE test_table (
    id NUMBER
);

The trigger fires automatically.

Important:
The trigger does not mean "Fire only for this particular table."

It means "Fire for DDL operations occurring in this schema that match the event."

5. What is a database-level DDL trigger?

A database-level trigger can respond to DDL events across the database, subject to Oracle privileges and trigger configuration.

CREATE OR REPLACE TRIGGER trg_database_ddl
AFTER CREATE OR ALTER OR DROP
ON DATABASE
BEGIN
    DBMS_OUTPUT.PUT_LINE('Database DDL occurred');
END;
/

Typically, creating database-level triggers requires elevated privileges.

6. Schema-level vs database-level DDL trigger

Feature Schema Level Database Level
Scope One schema Database-wide
Syntax ON SCHEMA ON DATABASE
Typical use Schema auditing Centralized database auditing
Privileges Lower than database-level Higher privileges generally required

7. How do I audit CREATE, ALTER and DROP?

Suppose we have an audit table:

CREATE TABLE ddl_audit (
    username       VARCHAR2(100),
    event_type     VARCHAR2(30),
    object_name    VARCHAR2(128),
    object_type    VARCHAR2(30),
    event_date     DATE
);

Create the trigger:

CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
    INSERT INTO ddl_audit
    (
        username,
        event_type,
        object_name,
        object_type,
        event_date
    )
    VALUES
    (
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        ORA_SYSEVENT,
        ORA_DICT_OBJ_NAME,
        ORA_DICT_OBJ_TYPE,
        SYSDATE
    );
END;
/

Now:

CREATE TABLE employees (
    emp_id NUMBER
);

will cause an audit record to be inserted.

8. What are ORA_SYSEVENT, ORA_DICT_OBJ_NAME and ORA_DICT_OBJ_TYPE?

These are very useful in DDL triggers.

ORA_SYSEVENT

Returns the event that caused the trigger.

CREATE
ALTER
DROP

ORA_DICT_OBJ_NAME

Returns the name of the object affected.

EMPLOYEES

ORA_DICT_OBJ_TYPE

Returns the object type.

TABLE
INDEX
VIEW
PROCEDURE

So this:

ORA_SYSEVENT

might return:

ALTER

while:

ORA_DICT_OBJ_NAME

returns:

EMPLOYEES

9. How do I find the current user?

You can use:

USER

or:

SYS_CONTEXT('USERENV', 'SESSION_USER')

Example:

CREATE OR REPLACE TRIGGER trg_ddl
AFTER CREATE ON SCHEMA
BEGIN
    DBMS_OUTPUT.PUT_LINE('Created by: ' || USER);
END;
/

For auditing, SYS_CONTEXT is often preferable because it gives access to additional session information.

10. How do I prevent DROP TABLE?

This is a common interview question.

You can use a DDL trigger to prevent certain DDL operations.

CREATE OR REPLACE TRIGGER trg_prevent_drop
BEFORE DROP ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Dropping tables is not allowed'
        );
    END IF;
END;
/

Now:

DROP TABLE employees;

will be rejected by the trigger.

11. Why use BEFORE DROP instead of AFTER DROP?

Because if you want to prevent the operation, the trigger must execute before the operation takes place.

For example:

BEFORE DROP ON SCHEMA

allows you to reject the operation.

An AFTER DROP trigger runs after the DDL operation has occurred.

Rule to remember:

BEFORE → validate / prevent
AFTER → audit / react

12. Can I restrict ALTER TABLE?

Yes.

CREATE OR REPLACE TRIGGER trg_no_alter
BEFORE ALTER ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
        RAISE_APPLICATION_ERROR(
            -20002,
            'ALTER TABLE is not allowed'
        );
    END IF;
END;
/

Now an operation such as:

ALTER TABLE employees ADD salary NUMBER;

will be blocked.

13. Can I prevent TRUNCATE?

Yes.

CREATE OR REPLACE TRIGGER trg_no_truncate
BEFORE TRUNCATE ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
        RAISE_APPLICATION_ERROR(
            -20003,
            'TRUNCATE is not allowed'
        );
    END IF;
END;
/

Then:

TRUNCATE TABLE employees;

will fail.

14. Can I create a trigger for only one type of DDL?

Yes. For example, only CREATE:

CREATE OR REPLACE TRIGGER trg_create_audit
AFTER CREATE ON SCHEMA
BEGIN
    INSERT INTO ddl_audit
    (
        username,
        event_type,
        object_name,
        object_type,
        event_date
    )
    VALUES
    (
        USER,
        ORA_SYSEVENT,
        ORA_DICT_OBJ_NAME,
        ORA_DICT_OBJ_TYPE,
        SYSDATE
    );
END;
/

15. Can one DDL trigger handle multiple events?

Yes.

CREATE OR REPLACE TRIGGER trg_ddl
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Event: ' || ORA_SYSEVENT
    );
END;
/

16. Can I use IF INSERTING, UPDATING, DELETING?

No.

Those conditions belong to DML triggers.

For DDL triggers, use:

ORA_SYSEVENT

For example:

IF ORA_SYSEVENT = 'DROP' THEN
    ...
END IF;

17. Can a DDL trigger use :OLD and :NEW?

No.

:OLD and :NEW are associated with row-level DML triggers.

DDL triggers instead use Oracle event attributes such as:

ORA_SYSEVENT
ORA_DICT_OBJ_NAME
ORA_DICT_OBJ_TYPE
ORA_DICT_OBJ_OWNER

18. What is ORA_DICT_OBJ_OWNER?

It returns the owner of the dictionary object affected by the DDL event.

CREATE OR REPLACE TRIGGER trg_ddl
AFTER CREATE ON SCHEMA
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Owner: ' || ORA_DICT_OBJ_OWNER
    );

    DBMS_OUTPUT.PUT_LINE(
        'Object: ' || ORA_DICT_OBJ_NAME
    );
END;
/

19. Can I audit DDL statements?

Yes.

For example:

CREATE TABLE ddl_audit (
    username       VARCHAR2(100),
    event_type     VARCHAR2(30),
    object_name    VARCHAR2(128),
    object_type    VARCHAR2(30),
    object_owner   VARCHAR2(128),
    event_date     DATE
);

Trigger:

CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
    INSERT INTO ddl_audit
    (
        username,
        event_type,
        object_name,
        object_type,
        object_owner,
        event_date
    )
    VALUES
    (
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        ORA_SYSEVENT,
        ORA_DICT_OBJ_NAME,
        ORA_DICT_OBJ_TYPE,
        ORA_DICT_OBJ_OWNER,
        SYSDATE
    );
END;
/

Then query:

SELECT *
FROM ddl_audit
ORDER BY event_date DESC;

20. Can I get the actual SQL statement that caused the trigger?

Yes. Oracle provides event attributes that can be used for DDL auditing, including SQL text information in appropriate trigger contexts.

A commonly used approach is ORA_SQL_TXT.

Conceptually:

CREATE OR REPLACE TRIGGER trg_ddl_sql
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
DECLARE
    n PLS_INTEGER;
BEGIN
    n := ORA_SQL_TXT(NULL);

    DBMS_OUTPUT.PUT_LINE(
        'DDL event: ' || ORA_SYSEVENT
    );
END;
/

ORA_SQL_TXT is more advanced and is worth learning when building a complete DDL auditing solution.

21. What happens if a DDL trigger raises an exception?

The DDL operation can fail.

Example:

CREATE OR REPLACE TRIGGER trg_block_drop
BEFORE DROP ON SCHEMA
BEGIN
    RAISE_APPLICATION_ERROR(
        -20010,
        'DROP operation is prohibited'
    );
END;
/

Then:

DROP TABLE employees;

fails because the trigger raises an error.

22. Does DDL automatically commit?

Yes. Oracle DDL has implicit transaction-control behavior, including commits around DDL operations.

This is an important difference from normal DML.

For example:

INSERT INTO employees VALUES (1, 'John');

CREATE TABLE test_table (id NUMBER);

The DDL has implicit commit implications for the surrounding transaction.

Interview point:
Don't treat DDL like INSERT, UPDATE, or DELETE because DDL has different transaction semantics.

23. Can I use COMMIT inside a DDL trigger?

You should not explicitly issue normal transaction-control statements such as:

COMMIT;

inside a trigger.

The DDL statement itself has Oracle's own transaction semantics.

For audit designs, let the DDL operation and trigger participate in Oracle's handling of the DDL transaction rather than trying to commit manually inside the trigger.

24. What is a database event trigger?

A trigger can also respond to database events, for example:

LOGON
LOGOFF
STARTUP
SHUTDOWN
SERVERERROR

Example:

CREATE OR REPLACE TRIGGER trg_logon
AFTER LOGON ON DATABASE
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'User logged in: ' || USER
    );
END;
/

This is a database event trigger, not a DDL trigger.

25. What is the difference between DML, DDL and database event triggers?

Trigger Type Example Events Typical Scope
DML INSERT, UPDATE, DELETE Table/View
DDL CREATE, ALTER, DROP, TRUNCATE Schema/Database
Database Event LOGON, STARTUP, SERVERERROR Database/Schema

26. Can I create a DDL trigger for a specific object?

A DDL trigger is not normally defined in the same object-specific manner as a DML trigger such as:

ON employees

Instead, you create a schema/database DDL trigger and inspect the affected object:

IF ORA_DICT_OBJ_NAME = 'EMPLOYEES'
   AND ORA_DICT_OBJ_TYPE = 'TABLE'
THEN
    ...
END IF;

Example:

CREATE OR REPLACE TRIGGER trg_emp_drop
BEFORE DROP ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_NAME = 'EMPLOYEES'
       AND ORA_DICT_OBJ_TYPE = 'TABLE'
    THEN
        RAISE_APPLICATION_ERROR(
            -20020,
            'EMPLOYEES table cannot be dropped'
        );
    END IF;
END;
/

27. Can DDL triggers cause performance problems?

Yes.

A DDL trigger executes whenever its event occurs.

For example:

AFTER CREATE OR ALTER OR DROP ON SCHEMA

could fire frequently in a development environment.

If the trigger performs expensive processing, DDL operations can become slower.

Keep DDL-trigger logic:

  • Simple
  • Reliable
  • Efficient
  • Focused on auditing or enforcement

28. What are the most common uses of DDL triggers?

Auditing

Who created/altered/dropped an object?
When?
What object?
What type?

Security

Prevent unauthorized:

  • DROP
  • ALTER
  • TRUNCATE

Change tracking

Track schema changes.

Governance

Enforce rules such as:

Certain tables cannot be dropped.
Certain objects cannot be altered.

⭐ Most Important Interview Questions

Q1. What is a DDL trigger?

A trigger that automatically fires in response to DDL events such as CREATE, ALTER, DROP, or TRUNCATE.

Q2. What is the difference between DML and DDL triggers?

DML → INSERT / UPDATE / DELETE
DDL → CREATE / ALTER / DROP / TRUNCATE

Q3. What is ORA_SYSEVENT?

It returns the DDL/database event that caused the trigger.

Example:

ORA_SYSEVENT

could return:

CREATE

Q4. What is ORA_DICT_OBJ_NAME?

It returns the name of the dictionary object affected by the event.

Q5. What is ORA_DICT_OBJ_TYPE?

It returns the type of the affected object, such as:

TABLE
INDEX
VIEW
PROCEDURE

Q6. Can DDL triggers use :OLD and :NEW?

No. Those are for row-level DML triggers.

Q7. How do you prevent DROP TABLE?

CREATE OR REPLACE TRIGGER trg_no_drop
BEFORE DROP ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'DROP TABLE is not allowed'
        );
    END IF;
END;
/

Q8. Schema-level vs database-level?

ON SCHEMA

means the trigger operates at schema scope.

ON DATABASE

means database-level scope and generally requires appropriate privileges.

Q9. What is the most common real-world use?

DDL auditing — recording who created, altered, or dropped database objects.

Q10. What is the difference between BEFORE and AFTER DDL triggers?

BEFORE CREATE/ALTER/DROP
    ↓
Can be used to validate/block the operation

AFTER CREATE/ALTER/DROP
    ↓
Useful for auditing/reacting after the operation

🔥 One Interview Scenario Worth Practicing

Requirement:

Nobody should be allowed to drop a table called CUSTOMERS, but all other tables can be dropped. Also, every CREATE/ALTER/DROP operation should be audited.

You could implement the restriction with:

CREATE OR REPLACE TRIGGER trg_protect_customers
BEFORE DROP ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE'
       AND ORA_DICT_OBJ_NAME = 'CUSTOMERS'
    THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'CUSTOMERS table cannot be dropped'
        );
    END IF;
END;
/

And separately implement a general audit trigger:

CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER CREATE OR ALTER OR DROP
ON SCHEMA
BEGIN
    INSERT INTO ddl_audit
    (
        username,
        event_type,
        object_name,
        object_type,
        object_owner,
        event_date
    )
    VALUES
    (
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        ORA_SYSEVENT,
        ORA_DICT_OBJ_NAME,
        ORA_DICT_OBJ_TYPE,
        ORA_DICT_OBJ_OWNER,
        SYSDATE
    );
END;
/
Key Interview Takeaway:

A DDL trigger works at schema/database scope, uses event attributes such as ORA_SYSEVENT and ORA_DICT_OBJ_NAME rather than :OLD/:NEW, and is commonly used for DDL auditing and preventing unauthorized schema changes.
```