Oracle Mutating Table Faqs With Examples

1. What is a mutating table error?

A mutating table error occurs when a row-level trigger tries to query or modify the same table that is currently being changed by the triggering DML statement.

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;
/

Then:

UPDATE employees
SET salary = salary + 1000;

This can produce:

ORA-04091: table EMPLOYEES is mutating

2. Why does the mutating-table error occur?

If Oracle is still processing rows for an UPDATE, a row-level trigger firing during that operation cannot safely query the same table and see a consistent state. That is why Oracle raises ORA-04091.

UPDATE EMPLOYEES
      ↓
Row 1 → Trigger
Row 2 → Trigger
Row 3 → Trigger
...
Row 100 → Trigger
      ↓
Statement completes

3. What is the classic mutating-table example?

A common example is checking a rule by querying the same table inside a row-level trigger.

CREATE OR REPLACE TRIGGER trg_salary_check
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
DECLARE
    v_avg_salary NUMBER;
BEGIN
    SELECT AVG(salary)
    INTO v_avg_salary
    FROM employees;

    IF :NEW.salary > v_avg_salary * 2 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Salary is too high'
        );
    END IF;
END;
/

Running an UPDATE on employees can result in ORA-04091 because the trigger queries the same table while it is being updated.

4. Which triggers commonly cause mutating-table errors?

The problem is specifically associated with row-level triggers that access their triggering table during the DML operation. Statement-level triggers do not have the same row-by-row mutating-table problem.

5. Does every query on the same table cause a mutating-table error?

The problematic case is a row-level trigger querying its own triggering table during the triggering DML operation.

6. Can a row-level trigger modify the same table?

Trying to modify the triggering table from its own row-level trigger can also cause problems, including mutating-table errors and recursive behavior.

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    UPDATE employees
    SET last_modified = SYSDATE
    WHERE employee_id = :NEW.employee_id;
END;
/

7. How can we avoid a mutating-table error?

Common solutions are to use a statement-level trigger, use a compound trigger, use a collection to store row information, move processing to the AFTER STATEMENT section, or redesign the business rule.

Solution 1 — Use a Statement-Level Trigger

8. How can a statement-level trigger help?

A statement-level trigger executes after the entire DML statement finishes, so the table is no longer mutating.

CREATE OR REPLACE TRIGGER trg_emp_check
AFTER UPDATE ON employees
DECLARE
    v_count NUMBER;
BEGIN
    SELECT COUNT(*)
    INTO v_count
    FROM employees;

    DBMS_OUTPUT.PUT_LINE(
        'Employee count: ' || v_count
    );
END;
/

9. What is the limitation of a statement-level trigger?

A statement-level trigger does not have individual row context, so you cannot use :OLD and :NEW in the statement-level section.

Solution 2 — Compound Trigger

10. Why is a compound trigger useful for mutating-table errors?

A compound trigger allows you to capture information during row processing and access it later in AFTER STATEMENT.

CREATE OR REPLACE TRIGGER trg_salary_check
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER

    AFTER STATEMENT IS
        v_avg_salary NUMBER;
    BEGIN
        SELECT AVG(salary)
        INTO v_avg_salary
        FROM employees;

        DBMS_OUTPUT.PUT_LINE(
            'Average salary: ' || v_avg_salary
        );
    END AFTER STATEMENT;

END;
/

11. How can a compound trigger collect changed employee IDs?

CREATE OR REPLACE TRIGGER trg_emp_collect
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER

```
TYPE emp_id_list IS
    TABLE OF employees.employee_id%TYPE;

g_emp_ids emp_id_list := emp_id_list();

AFTER EACH ROW IS
BEGIN
    g_emp_ids.EXTEND;
    g_emp_ids(g_emp_ids.COUNT) := :NEW.employee_id;
END AFTER EACH ROW;

AFTER STATEMENT IS
BEGIN
    FOR i IN 1 .. g_emp_ids.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(
            'Updated Employee: ' || g_emp_ids(i)
        );
    END LOOP;
END AFTER STATEMENT;
```

END;
/

12. How does this solve the mutating-table problem?

During AFTER EACH ROW, the trigger only collects data and does not query employees. After the update completes, AFTER STATEMENT runs and can safely query the table.

Solution 3 — Use a Collection

13. Why use a collection?

A collection allows the row-level trigger section to store information without querying the mutating table.

CREATE OR REPLACE TRIGGER trg_emp_changes
FOR UPDATE ON employees
COMPOUND TRIGGER

    TYPE emp_list IS
        TABLE OF employees.employee_id%TYPE;

    g_ids emp_list := emp_list();

    AFTER EACH ROW IS
    BEGIN
        g_ids.EXTEND;
        g_ids(g_ids.COUNT) := :NEW.employee_id;
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN
        FOR i IN 1 .. g_ids.COUNT LOOP
            DBMS_OUTPUT.PUT_LINE(
                'Employee: ' || g_ids(i)
            );
        END LOOP;
    END AFTER STATEMENT;

END;
/

Solution 4 — Use an Autonomous Transaction?

No. It should not be treated as a general solution. For mutating-table problems, prefer compound triggers, statement-level processing, collections, or redesign.

14. Can a package help solve a mutating-table problem?

Historically, one solution was to store values in a package collection and process them later in a statement-level trigger. A compound trigger is usually cleaner.

15. Classic Interview Question

Question: Why does this trigger cause ORA-04091?

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
FOR EACH ROW
DECLARE
    v_total NUMBER;
BEGIN
    SELECT SUM(salary)
    INTO v_total
    FROM employees;
END;
/

Answer: Because employees is being modified and the row-level trigger attempts to query the same table while it is mutating.

16. How would you fix that trigger?

Move the query to statement-level processing.

CREATE OR REPLACE TRIGGER trg_emp
AFTER UPDATE ON employees
DECLARE
    v_total NUMBER;
BEGIN
    SELECT SUM(salary)
    INTO v_total
    FROM employees;

    DBMS_OUTPUT.PUT_LINE(
        'Total salary: ' || v_total
    );
END;
/

17. What if I need :NEW values as well?

Use a compound trigger.

CREATE OR REPLACE TRIGGER trg_emp
FOR UPDATE ON employees
COMPOUND TRIGGER

    TYPE id_list IS
        TABLE OF employees.employee_id%TYPE;

    g_ids id_list := id_list();

    AFTER EACH ROW IS
    BEGIN
        g_ids.EXTEND;
        g_ids(g_ids.COUNT) := :NEW.employee_id;
    END AFTER EACH ROW;

    AFTER STATEMENT IS
        v_total NUMBER;
    BEGIN
        SELECT SUM(salary)
        INTO v_total
        FROM employees;

        DBMS_OUTPUT.PUT_LINE(
            'Total salary: ' || v_total
        );

        FOR i IN 1 .. g_ids.COUNT LOOP
            DBMS_OUTPUT.PUT_LINE(
                'Changed employee: ' || g_ids(i)
            );
        END LOOP;
    END AFTER STATEMENT;

END;
/

18. Is a mutating-table error a compilation error?

Usually no. The trigger can compile successfully, but the error occurs at runtime when the triggering DML statement executes.

19. What is ORA-04091?

The key Oracle error is ORA-04091: table string.string is mutating, trigger/function may not see it. It indicates that a row-level trigger or function called from it is attempting to access a table that is currently undergoing the triggering DML operation.

20. What is ORA-04088?

You may also see ORA-04088: error during execution of trigger, which commonly appears along with an underlying error such as ORA-04091.

21. Does a SELECT from another table cause a mutating-table error?

Normally no. The mutating-table issue is specifically about accessing the table currently undergoing the triggering operation.

22. Does a foreign-key relationship automatically cause a mutating-table error?

No. The issue is typically caused by a row-level trigger or function attempting to access the table that is currently being modified.

23. Can a function called by a trigger cause a mutating-table error?

Yes. If the function queries the same table that is being modified, the same mutating-table problem can occur.

Quick Interview Table

Question Answer
What is a mutating table? A table currently being modified by the triggering DML statement
Common error? ORA-04091
Usually associated with? Row-level triggers
Can the trigger query another table? Yes
Can the trigger safely query the triggering table row-by-row? Generally no
Can AFTER STATEMENT query the table? Yes
Best modern solution? Compound trigger when row + statement processing is needed
Can collections help? Yes
Does :OLD/:NEW cause the problem? No
Can a called function cause it? Yes

Key Interview Point

A mutating-table error (ORA-04091) occurs when row-level trigger processing attempts to access the table that is currently being modified. The usual solutions are to move the table access to statement-level processing or use a compound trigger to collect row-level information and process it after the statement completes.

No comments:

Post a Comment