0
votes

I have the following table:

CREATE TABLE individuo(
  codigo NUMBER(8) PRIMARY KEY,
  nombre VARCHAR2(20) NOT NULL,
  valor NUMBER(8) NOT NULL CHECK (valor > 0),
  padre NUMBER(8) REFERENCES individuo,
  nro_hijos NUMBER(8) NOT NULL CHECK (nro_hijos >=0),
  CHECK(padre <> codigo)
);

Where padre means father and nro_hijos means number of children.

I need a trigger that when I insert or delete a row where padre IS NOT NULL, it increases or decreases the nro_hijos correspondingly.

I tried this:

CREATE OR REPLACE TRIGGER individuo_parent_increment
AFTER INSERT OR DELETE ON INDIVIDUO FOR EACH ROW WHEN (NEW.padre IS NOT NULL)
    BEGIN
        IF INSERTING THEN
            UPDATE INDIVIDUO SET NRO_HIJOS = NRO_HIJOS + 1 WHERE CODIGO = :NEW.PADRE;
        END IF;
    END;

but it thew ORA-04091 (table individuo is mutating, trigger/function may not see it)

I also tried using a COMPOUND TRIGGER but same error occured.

I have a different trigger already setup:

CREATE OR REPLACE TRIGGER individuo_initial_children
BEFORE INSERT ON INDIVIDUO
    FOR EACH ROW
    BEGIN
        :NEW.NRO_HIJOS := 0;
    END;
2
I think hold the information of the children within another table along with primary-foreign key relationship with this current table, and count that newly cretaed table for an individual father whenever number of children is needed. eg. give up creating a trigger for counting for the sake of the DB design. - Barbaros Özhan
I need a trigger though, it's an academic assignment :c - Rendxn

2 Answers

0
votes

I ended up doing the following using a compound trigger [1][2]:

CREATE OR REPLACE TRIGGER individuo_compound_trigger
FOR INSERT ON INDIVIDUO
COMPOUND TRIGGER

    -- Create a list of number
    TYPE number_arr IS TABLE OF INDIVIDUO.PADRE%TYPE
    INDEX BY BINARY_INTEGER;
    dads number_arr;

    -- The other trigger I needed
    BEFORE EACH ROW IS
    BEGIN
        :NEW.NRO_HIJOS := 0;
    END BEFORE EACH ROW;

    -- Store all affected padre.
    AFTER EACH ROW IS BEGIN
        IF :NEW.PADRE IS NOT NULL THEN
            dads(dads.COUNT + 1) := :NEW.PADRE;
        END IF;
    END AFTER EACH ROW;

    -- Loop through them and update them here:
    AFTER STATEMENT IS BEGIN
        FOR dad IN 1..dads.COUNT
            LOOP
                UPDATE INDIVIDUO SET NRO_HIJOS = NRO_HIJOS + 1 WHERE CODIGO = dads(dad);
            END LOOP;
    END AFTER STATEMENT;

END individuo_compound_trigger;

NOTE: This is an academic assignmentin which I was specifically asked to create a trigger, I do not know if this is a good solution for other purposes.

0
votes

A compound trigger is the correct solution if maintaining a running total of number of children is required. A better design (imho)would be to just derive that when needed, perhaps in a view. As an assignment it is good to see you looked for and found this method. There is one slight change I would make. You should stop thinking in terms of loops. Rather than looping through the array just use FORALL resulting is a single statement to handle all the updates.

-- Update NRO_HIJOS for all dads in array 
after statement is begin
    forall dad in 1..dads.count
            update individuo 
               set nro_hijos = nro_hijos + 1 
             where codigo = dads(dad);
end after statement;