/* To get the place trigger, replace taxonlabel with place and remove references to matched_label_id. */ ----- Functions CREATE OR REPLACE FUNCTION taxonlabel_update_ancestors(new taxonlabel, parent_id_ integer, old_parent_id integer DEFAULT NULL::integer) RETURNS void AS $BODY$ BEGIN IF parent_id_ IS DISTINCT FROM old_parent_id THEN DECLARE -- These include the parent itself old_ancestors integer[] := ( SELECT array_agg(ancestor_id) FROM taxonlabel_relationship WHERE descendant_id = old_parent_id ); new_ancestors integer[] := ( SELECT array_agg(ancestor_id) FROM taxonlabel_relationship WHERE descendant_id = parent_id_ ); descendant_id_ taxonlabel_relationship.descendant_id%TYPE; ancestor_id_ taxonlabel_relationship.ancestor_id%TYPE; BEGIN FOR descendant_id_ IN -- also includes self SELECT descendant_id FROM taxonlabel_relationship WHERE ancestor_id = new.taxonlabel_id LOOP -- Delete old parent's ancestors DELETE FROM taxonlabel_relationship WHERE descendant_id = descendant_id_ AND ancestor_id = ANY (old_ancestors) ; -- Add new parent's ancestors FOR ancestor_id_ IN SELECT unnest(new_ancestors) LOOP BEGIN INSERT INTO taxonlabel_relationship (descendant_id, ancestor_id) SELECT descendant_id_, ancestor_id_ ; EXCEPTION WHEN unique_violation THEN RAISE WARNING '%', SQLERRM; END; END LOOP; END LOOP; END; END IF; /* Note: We don't need an ON DELETE trigger to update the descendants' ancestors when a node is deleted, because the taxonlabel_relationship.ancestor_id foreign key is ON DELETE CASCADE. */ END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION taxonlabel_update_ancestors_on_insert() RETURNS trigger AS $BODY$ BEGIN /* Add "ancestor" for this node. This is useful for queries, because you don't have to separately test if the leaf node is the one you're looking for, in addition to that leaf node's ancestors. */ INSERT INTO taxonlabel_relationship (descendant_id, ancestor_id) VALUES (new.taxonlabel_id, new.taxonlabel_id) ; PERFORM taxonlabel_update_ancestors(new, new.parent_id); PERFORM taxonlabel_update_ancestors(new, NULLIF(new.matched_label_id, new.taxonlabel_id)); RETURN new; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION taxonlabel_update_ancestors_on_update() RETURNS trigger AS $BODY$ BEGIN PERFORM taxonlabel_update_ancestors(new, new.parent_id, old.parent_id); PERFORM taxonlabel_update_ancestors(new, NULLIF(new.matched_label_id, new.taxonlabel_id), NULLIF(old.matched_label_id, old.taxonlabel_id)); RETURN new; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ----- Ancestors table CREATE TABLE taxonlabel_relationship ( descendant_id integer NOT NULL, ancestor_id integer NOT NULL, relationship relationship NOT NULL DEFAULT 'is included in'::relationship, -- The type of relationship. CONSTRAINT taxonlabel_relationship_pkey PRIMARY KEY (descendant_id , ancestor_id ), CONSTRAINT taxonlabel_relationship_ancestor_id_fkey FOREIGN KEY (ancestor_id) REFERENCES taxonlabel (taxonlabel_id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT taxonlabel_relationship_descendant_id_fkey FOREIGN KEY (descendant_id) REFERENCES taxonlabel (taxonlabel_id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE ) WITH ( OIDS=FALSE ); ALTER TABLE taxonlabel_relationship OWNER TO bien; COMMENT ON TABLE taxonlabel_relationship IS 'Stores the accepted ancestors of a taxonlabel. Auto-populated, so should not be manually modified.'; COMMENT ON COLUMN taxonlabel_relationship.relationship IS 'The type of relationship.'; CREATE INDEX taxonlabel_relationship_descendants ON taxonlabel_relationship USING btree (ancestor_id , descendant_id ); ----- Triggers CREATE TRIGGER taxonlabel_update_ancestors_on_insert AFTER INSERT ON taxonlabel FOR EACH ROW EXECUTE PROCEDURE taxonlabel_update_ancestors_on_insert(); CREATE TRIGGER taxonlabel_update_ancestors_on_update AFTER UPDATE ON taxonlabel FOR EACH ROW EXECUTE PROCEDURE taxonlabel_update_ancestors_on_update();