Witam serdecznie. Mam tabelkę, którą można uprościć do 3 pól.
  1. CREATE TABLE project
  2. (
  3. project_id serial NOT NULL,
  4. project_title varchar(25),
  5. parent_id int8,
  6. project_hours int8 DEFAULT 0,
  7. project_child_hours int8 DEFAULT 0
  8. ) WITHOUT OIDS;


Problem polega na tym by automatycznie zmieniać wartość pola project_child_hours.
Kolumna project_hours zawiera szacowany czas wykonania zadania/projektu. Jeśli projekt ma dzieci (w tym przypadku węzeł Implementacja) to wartość wyświetlana przy edycji oraz podglądzie tego zadania będzie się równać sumie czasu potrzebnego na wykonanie podzadań - Warstwy prezentacji oraz Logiki biznesowej.

Przykład drzewa:
Kod
Projekt głowny                            [project_hours: 16, project_child_hours: 16]
`- Opracowanie struktory bazy danych      [project_hours: 4,  project_child_hours: 0]
`- Implementacja                          [project_hours: 12, project_child_hours: 12]
   `- Warstwa prezentacji                  [project_hours: 6,  project_child_hours: 0]
   `- Logika biznesowa                     [project_hours: 6,  project_child_hours: 0]


Stworzyłem procedurę która działa tak jak powinna - robi update elementu nadrzędnego dodając lub odejmując czas do kolumny project_child_hours. Problem w tym, że nie powoduje ona rekurencji (nie jest już wywoływana dla elementu nadrzędnego). Przykład: zmieniam czas w zadaniu Logika biznesowa na 8. Procedura robi update zadania Implementacja i dodaje do kolumny project_child_hours 2. Na tym etapie wszystko się zatrzymuje.
Chciałbym wykonać procedurę w taki sposób by poszła ona wyżej i zmieniła wartość project_child_hours dla Projektu głownego na 18 (dodała niżej dodane godziny).
W końcu postanowiłem napisać regułę, lecz nie wiem czemu zapętła się i w końcu postgres ją kiluje.

Proszę o podpowiedzi jak wykonać procedurę tak, by zadziałała w górę drzewa aż do elementu nadrzędnego.

Procedura wyzwalana (wyraźniejszy kod http://phpfi.com/91942):
Kod
CREATE OR REPLACE FUNCTION UPDATE_PROJECT_CHILD_HOURS() RETURNS trigger AS $PROJECT_CHILD_HOURS$
DECLARE
    hours     int8 := 0;
    parent_id int8 := 0;
BEGIN
    IF (TG_OP = 'INSERT') THEN -- isnert - dodajemy
  hours     = NEW.project_hours;
  parent_id = NEW.parent_id;
    ELSIF (TG_OP = 'UPDATE') THEN -- update, trzeba rozpoznac czy godziny sie zwiekszyly czy zmniejszyly
  IF (NEW.project_hours > OLD.project_hours) THEN
     hours = NEW.project_hours - OLD.project_hours;
  ELSIF (NEW.project_hours < OLD.project_hours) THEN
     hours = NEW.project_hours - OLD.project_hours;
     --RAISE EXCEPTION 'Liczba godzin %', hours;
  END IF;
  parent_id = NEW.parent_id;
    ELSIF (TG_OP = 'DELETE') THEN -- usuwamy projekt - zatem odejmujemy od parenta jego godziny
  hours     = -OLD.project_chours;
  parent_id = OLD.parent_id;
    END IF;

    IF( parent_id > 0 AND hours <> 0 ) THEN -- jesli zmieniaja sie godziny - robimy update
  UPDATE project SET project_child_hours = project_child_hours + hours WHERE project_id = parent_id;
    END IF;

    IF(TG_OP <> 'DELETE') THEN -- dodawanie/usuwanie - jest NEW
  RETURN NEW;
    ELSE -- usuwanie - nie ma NEW
  RETURN OLD;
    END IF;
END;
$PROJECT_CHILD_HOURS$ Language plpgsql;


Reguła (wyraźniejszy kod http://phpfi.com/91943):
Kod
CREATE OR REPLACE RULE UPDATE_PROJECT_CHILD_HOURS_FOR_ROW AS
    ON UPDATE TO project WHERE (NEW.parent_id > 0 AND NEW.project_hours <> OLD.project_hours) DO
  UPDATE project SET project_child_hours = project_child_hours+(NEW.project_hours - OLD.project_hours)
  WHERE parent_id > 0 AND project_id = NEW.parent_id;


Rozwiązanie problemu
W końcu udało mi się rozwiązać problem - wystarczyło wyjść na dwór zaczerpnąć świerzego powietrza. winksmiley.jpg
Oto dwie funkcje, które rozwiązują powstałe zagadnienie:
Kod
CREATE OR REPLACE FUNCTION UPDATE_HOURS_ON_TREE(int8,int8) RETURNS INT2 AS $$
DECLARE
    myvar int8;
BEGIN
    PERFORM parent_id FROM project WHERE project_id = $1;
    IF FOUND THEN
  SELECT INTO myvar parent_id FROM project WHERE project_id = $1;
    ELSE
  RETURN 0;
    END IF;

    UPDATE project SET project_child_hours = project_child_hours + $2 WHERE project_id = $1;
    IF( myvar > 0 ) THEN
  PERFORM project_id FROM project WHERE project_id = myvar;
  IF FOUND THEN
     RAISE NOTICE 'EXECUTE PERFORM';
     PERFORM UPDATE_HOURS_ON_TREE(myvar,$2);
  END IF;
    END IF;
    RETURN 1;
END;
$$ Language plpgsql;

CREATE OR REPLACE FUNCTION update_project_child_hours()
  RETURNS "trigger" AS
$BODY$
DECLARE
    hours     int8 := 0;
    parent_id int8 := 0;
    parent___ int8 := 0;
BEGIN
    IF (TG_OP = 'INSERT') THEN -- isnert - dodajemy
  hours     = NEW.project_hours;
  parent_id = NEW.parent_id;
    ELSIF (TG_OP = 'UPDATE') THEN -- update, trzeba rozpoznac czy godziny sie zwiekszyly czy zmniejszyly
  hours = NEW.project_hours - OLD.project_hours;
  parent_id = NEW.parent_id;
    ELSIF (TG_OP = 'DELETE') THEN -- usuwamy projekt - zatem odejmujemy od parenta jego godziny
  hours     = -OLD.project_hours;
  parent_id = OLD.parent_id;
    END IF;

    IF( parent_id > 0 AND hours <> 0 ) THEN -- jesli zmieniaja sie godziny - robimy update
  PERFORM UPDATE_HOURS_ON_TREE(parent_id,hours);
    END IF;

    IF(TG_OP <> 'DELETE') THEN -- dodawanie/usuwanie - jest NEW
  RETURN NEW;
    ELSE -- usuwanie - nie ma NEW
  RETURN OLD;
    END IF;
END;
$BODY$
LANGUAGE 'plpgsql';

Wiem, że rozwiązanie nie jest najładniejsze (dublujące się zapytania), ale działa. :]