/* To get the place trigger, replace taxonconcept with place and remove references to matched_concept_id. */ ----- Functions CREATE OR REPLACE FUNCTION taxonconcept_update_ancestors(new taxonconcept, old_parent_id integer DEFAULT NULL::integer) RETURNS void AS $BODY$ DECLARE -- Use matched_concept_id's ancestors instead if available parent_id_ taxonconcept.taxonconcept_id%TYPE := COALESCE( NULLIF(new.matched_concept_id, new.taxonconcept_id), new.parent_id); 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 taxonconcept_ancestor WHERE taxonconcept_id = old_parent_id ); new_ancestors integer[] := ( SELECT array_agg(ancestor_id) FROM taxonconcept_ancestor WHERE taxonconcept_id = parent_id_ ); descendant_id integer; BEGIN FOR descendant_id IN -- also includes self SELECT taxonconcept_id FROM taxonconcept_ancestor WHERE ancestor_id = new.taxonconcept_id LOOP -- Delete old parent's ancestors DELETE FROM taxonconcept_ancestor WHERE taxonconcept_id = descendant_id AND ancestor_id = ANY (old_ancestors) ; -- Add new parent's ancestors INSERT INTO taxonconcept_ancestor (taxonconcept_id, ancestor_id) SELECT descendant_id, unnest(new_ancestors) ; 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 taxonconcept_ancestor.ancestor_id foreign key is ON DELETE CASCADE. */ END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION taxonconcept_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 taxonconcept_ancestor (taxonconcept_id, ancestor_id) VALUES (new.taxonconcept_id, new.taxonconcept_id) ; PERFORM taxonconcept_update_ancestors(new); RETURN new; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; CREATE OR REPLACE FUNCTION taxonconcept_update_ancestors_on_update() RETURNS trigger AS $BODY$ BEGIN PERFORM taxonconcept_update_ancestors(new, COALESCE( NULLIF(old.matched_concept_id, old.taxonconcept_id), old.parent_id)); RETURN new; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ----- Ancestors table CREATE TABLE taxonconcept_ancestor ( taxonconcept_id integer NOT NULL, ancestor_id integer NOT NULL, CONSTRAINT taxonconcept_ancestor_pkey PRIMARY KEY (taxonconcept_id , ancestor_id ), CONSTRAINT taxonconcept_ancestor_ancestor_id_fkey FOREIGN KEY (ancestor_id) REFERENCES taxonconcept (taxonconcept_id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT taxonconcept_ancestor_taxonconcept_id_fkey FOREIGN KEY (taxonconcept_id) REFERENCES taxonconcept (taxonconcept_id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE ) WITH ( OIDS=FALSE ); COMMENT ON TABLE taxonconcept_ancestor IS 'Stores the accepted ancestors of a taxonconcept. Auto-populated, so should not be manually modified.'; CREATE INDEX taxonconcept_ancestor_descendants ON taxonconcept_ancestor USING btree (ancestor_id , taxonconcept_id ); ----- Triggers CREATE TRIGGER taxonconcept_update_ancestors_on_insert AFTER INSERT ON taxonconcept FOR EACH ROW EXECUTE PROCEDURE taxonconcept_update_ancestors_on_insert(); CREATE TRIGGER taxonconcept_update_ancestors_on_update AFTER UPDATE ON taxonconcept FOR EACH ROW EXECUTE PROCEDURE taxonconcept_update_ancestors_on_update();