Search code examples
sqloracle-databasetriggers

Oracle PL/SQL trigger won't allow to select records from same table and update into another table


I have condition where i need to update table x1 with the value of e1,e2,and e3. Assume e1 is primary key. When data gets modified on X2, I need to pull max of e2 and max of e3 to update the table X1. I wrote trigger like this however getting mutating table error. I don't want to use PRAGMA AUTONOMOUS TRANSACTION. Your help would be appriciated.

    create or replace TRIGGER TR_UPDATE_ON_X2
AFTER INSERT OR UPDATE OR DELETE ON X2
FOR EACH ROW
DECLARE
  cnt NUMBER;
  E1 VARCHAR2(20 BYTE);
  E2 NUMBER(3,0);
  E3 VARCHAR2(50 BYTE);
BEGIN
  IF INSERTING OR UPDATING THEN
    SELECT COUNT(1) INTO CNT FROM X1 WHERE E1 = :NEW.E1;
    IF(CNT =0)THEN
       INSERT INTO X1 (E1, E2, E3)  VALUES(:NEW.E1,:NEW.E2,:NEW.E3);
    ELSE  
        SELECT E1,E2,E3 INTO E1,  E2, E3 FROM X1 WHERE E1 = :NEW.E1;
        IF(:NEW.E1 > E1) THEN
            E1 := :NEW.E1;
        END IF;
        IF(:NEW.E2 > E2) THEN
            E2 := :NEW.E2;
        END IF;
        IF(:NEW.E3 > E3) THEN
            E3 := :NEW.E3;
        END IF;
        UPDATE X1 SET E1 = E1, E2 = E2, E3 = E3 WHERE E1 = :NEW.E1;
    END IF;
  ELSIF DELETING THEN
    SELECT COUNT(1) INTO CNT FROM X2 WHERE CONTRACTORSOIDENTIFIER = :NEW.CONTRACTORSOIDENTIFIER;
    IF(CNT != 0)THEN
        SELECT E1,MAX(E2),MAX(E3) INTO E1,  E2, E3 FROM X2 WHERE E1 = :NEW.E1 GROUP BY X2.E1;
        IF(:NEW.E1 > E1) THEN
            E1 := :NEW.E1;
        END IF;
        IF(:NEW.E2 > E2) THEN
            E2 := :NEW.E2;
        END IF;
        IF(:NEW.E3 > E3) THEN
            E3 := :NEW.E3;
        END IF;
        UPDATE X1 SET E1 = E1, E2 = E2, E3 = E3 WHERE E1 = :NEW.E1;
    ELSE
        DELETE FROM X1 WHERE E1 = :NEW.E1;
    END IF;
  END IF;
END;

Solution

  • So, there can be many rows per e1 in x2. And for each e1 that exists in x2 there exists one row in x1. The x1 row contains the maximum values for e2 and e3 per e1.

    Your INSERTING OR UPDATING part looks okay, apart from naming your variables identical to your columns. As William Robertson already pointed out in the request comments, SET E1 = E1 just means keep the value as is. The DBMS doesn't guess that the second E1 is supposed to be the other E1. It is common to precede variables with v_ to make them distinct from column names.

    Your DELETING has a main issue. The problem is that by deleting an x2 row, the x1 maximums must be evaluated anew for the e1. This means that we must select from x2 while the table is in the process of changing. (As a delete statement can affect more than one row, AFTER ROW does not necessarily mean AFTER STATEMENT, so we may be in the middle of changes and can not deterministicly say what's yet in the table and what not.)

    The solution to this is a compound trigger. This trigger type combines before/after row and before/after statement. So you can look at the rows, see which e1 are affected and then after all the rows processing you update x1 for all affected e1.

    Here is a simple trigger (that certainly leaves room for optimization :-)

    CREATE OR REPLACE TRIGGER tr_update_x1_on_x2
    FOR INSERT OR UPDATE OR DELETE ON x2
    COMPOUND TRIGGER
      v_array SYS.ORA_MINING_NUMBER_NT := SYS.ORA_MINING_NUMBER_NT();
    
      AFTER EACH ROW IS
      BEGIN
        -- Add e1 to the array for a later insert/update/delete in x1
        v_array.EXTEND;
        v_array(v_array.COUNT) := COALESCE(:new.e1, :old.e1);
      END AFTER EACH ROW;
    
      AFTER STATEMENT IS
      BEGIN
        -- Delete rows from x1 where the e1 no longer exists in x2
        DELETE FROM x1
        WHERE e1 IN (select * from table(v_array))
        AND e1 NOT IN (select e1 from x2);
    
        -- Insert/update x1 rows with the new maximums found in x2
        MERGE INTO x1
        USING
        (
          SELECT e1, MAX(e2) AS max_e2, MAX(e3) AS max_e3
          FROM x2
          WHERE e1 IN (select * from table(v_array))
          GROUP BY e1
        ) src ON (src.e1 = x1.e1)
        WHEN MATCHED THEN
          UPDATE SET e2 = src.max_e2, e3 = src.max_e3
        WHEN NOT MATCHED THEN
          INSERT (e1, e2, e3) VALUES (src.e1, src.max_e2, src.max_e3)
      END AFTER STATEMENT;
    END tr_update_x1_on_x2;