CREATE ns_tree ( id SERIAL, left_key INTEGER NOT NULL, right_key INTEGER NOT NULL, level INTEGER NOT NULL DEFAULT 0, tree INTEGER NOT NULL, parent_id INTEGER NOT NULL DEFAULT 0, _trigger_lock_update BOOLEAN NOT NULL DEFAULT FALSE, _trigger_for_delete BOOLEAN NOT NULL DEFAULT FALSE, field_1 ..., ... PRIMARY KEY (id) );Actually - _trigger_lock_update and _trigger_for_delete , are our auxiliary fields. Immediately make the function a blocking tree for change until the transaction is completed: SQL code (2)
CREATE OR REPLACE FUNCTION lock_ns_tree (integer) RETURNS boolean AS $ BODY $ DECLARE tree_id ALIAS FOR $ 1; _id INTEGER; BEGIN SELECT id INTO _id FROM ns_tree WHERE tree = tree_id FOR UPDATE; RETURN TRUE; END; $ BODY $ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION lock_ns_tree (integer) OWNER TO user;
CREATE OR REPLACE FUNCTION ns_tree_before_insert_func () RETURNS trigger AS $ BODY $ DECLARE _left_key INTEGER; _level INTEGER; _tmp_left_key INTEGER; _tmp_right_key INTEGER; _tmp_level INTEGER; _tmp_id INTEGER; _tmp_parent_id INTEGER; BEGIN PERFORM lock_ns_tree (NEW.tree); - You can not put these fields handles: NEW._trigger_for_delete: = FALSE; NEW._trigger_lock_update: = FALSE; _left_key: = 0; _level: = 0; - If we indicated a parent: IF NEW.parent_id IS NOT NULL AND NEW.parent_id> 0 THEN SELECT right_key, "level" + 1 INTO _left_key, _level FROM ns_tree WHERE id = NEW.parent_id AND tree = NEW.tree; END IF; - If we specified the left key: IF NEW.left_key IS NOT NULL AND NEW.left_key> 0 AND (_left_key IS NULL OR _left_key = 0) THEN SELECT id, left_key, right_key, "level", parent_id INTO _tmp_id, _tmp_left_key, _tmp_right_key, _tmp_level, _tmp_parent_id FROM ns_tree WHERE tree = NEW.tree AND (left_key = NEW.left_key OR right_key = NEW.left_key); IF _tmp_left_key IS NOT NULL AND _tmp_left_key> 0 AND NEW.left_key = _tmp_left_key THEN NEW.parent_id: = _tmp_parent_id; _left_key: = NEW.left_key; _level: = _tmp_level; ELSIF _tmp_left_key IS NOT NULL AND _tmp_left_key> 0 AND NEW.left_key = _tmp_right_key THEN NEW.parent_id: = _tmp_id; _left_key: = NEW.left_key; _level: = _tmp_level + 1; END IF; END IF; - If the parent or left key is not specified, or we did not find: IF _left_key IS NULL OR _left_key = 0 THEN SELECT MAX (right_key) + 1 INTO _left_key FROM ns_tree WHERE tree = NEW.tree; IF _left_key IS NULL OR _left_key = 0 THEN _left_key: = 1; END IF; _level: = 0; NEW.parent_id: = 0; END IF; - Install the obtained keys for the node: NEW.left_key: = _left_key; NEW.right_key: = _left_key + 1; NEW. "Level": = _level; - We form a tree in the tree at the insertion point: UPDATE ns_tree SET left_key = left_key + CASE WHEN left_key> = _left_key THEN 2 ELSE 0 END, right_key = right_key + 2, _trigger_lock_update = TRUE WHERE tree = NEW.tree AND right_key> = _left_key; RETURN NEW; END; $ BODY $ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION ns_tree_before_insert_func () OWNER TO user; CREATE TRIGGER ns_tree_before_insert_tr BEFORE INSERT ON ns_tree FOR EACH ROW EXECUTE PROCEDURE ns_tree_before_insert_func ();Now some explanations:
CREATE OR REPLACE FUNCTION ns_tree_before_update_func () RETURNS trigger AS $ BODY $ DECLARE _left_key INTEGER; _level INTEGER; _skew_tree INTEGER; _skew_level INTEGER; _skew_edit INTEGER; _tmp_left_key INTEGER; _tmp_right_key INTEGER; _tmp_level INTEGER; _tmp_id INTEGER; _tmp_parent_id INTEGER; BEGIN PERFORM lock_ns_tree (OLD.tree); - And is it worth us to do anything at all: IF NEW._trigger_lock_update = TRUE THEN NEW._trigger_lock_update: = FALSE; IF NEW._trigger_for_delete = TRUE THEN NEW = OLD; NEW._trigger_for_delete = TRUE; RETURN NEW; END IF; RETURN NEW; END IF; - We reset the values ​​of the fields that the user can not change: NEW._trigger_for_delete: = FALSE; NEW.tree: = OLD.tree; NEW.right_key: = OLD.right_key; NEW. "Level": = OLD. "Level"; IF NEW.parent_id IS NULL THEN NEW.parent_id: = 0; END IF; - Check whether there are changes associated with the tree structure IF NEW.parent_id = OLD.parent_id AND NEW.left_key = OLD.left_key THEN RETURN NEW; END IF; - We are still rebuilding the tree, well, let's start: _left_key: = 0; _level: = 0; _skew_tree: = OLD.right_key - OLD.left_key + 1; - Determine where we transfer it: - If parent_id is changed: IF NEW.parent_id <> OLD.parent_id THEN - If in submission to another evil: IF NEW.parent_id> 0 THEN SELECT right_key, level + 1 INTO _left_key, _level FROM ns_tree WHERE id = NEW.parent_id AND tree = NEW.tree; - Otherwise, we transfer to the root of the tree: ELSE SELECT MAX (right_key) + 1 INTO _left_key FROM ns_tree WHERE tree = NEW.tree; _level: = 0; END IF; - If suddenly the parent is in the range of the node being moved, check: IF _left_key IS NOT NULL AND _left_key> 0 AND _left_key> OLD.left_key AND _left_key <= OLD.right_key THEN NEW.parent_id: = OLD.parent_id; NEW.left_key: = OLD.left_key; RETURN NEW; END IF; END IF; - If left_key is specified, but parent_id is not IF _left_key IS NULL OR _left_key = 0 THEN SELECT id, left_key, right_key, "level", parent_id INTO _tmp_id, _tmp_left_key, _tmp_right_key, _tmp_level, _tmp_parent_id FROM ns_tree WHERE tree = NEW.tree AND (right_key = NEW.left_key OR right_key = NEW.left_key - 1) LIMIT 1; IF _tmp_left_key IS NOT NULL AND _tmp_left_key> 0 AND NEW.left_key - 1 = _tmp_right_key THEN NEW.parent_id: = _tmp_parent_id; _left_key: = NEW.left_key; _level: = _tmp_level; ELSIF _tmp_left_key IS NOT NULL AND _tmp_left_key> 0 AND NEW.left_key = _tmp_right_key THEN NEW.parent_id: = _tmp_id; _left_key: = NEW.left_key; _level: = _tmp_level + 1; ELSIF NEW.left_key = 1 THEN NEW.parent_id: = 0; _left_key: = NEW.left_key; _level: = 0; ELSE NEW.parent_id: = OLD.parent_id; NEW.left_key: = OLD.left_key; RETURN NEW; END IF; END IF; - Now we know where we move the tree. _skew_level: = _level - OLD. "level"; IF _left_key> OLD.left_key THEN - Move up the tree _skew_edit: = _left_key - OLD.left_key - _skew_tree; UPDATE ns_tree SET left_key = CASE WHEN right_key <= OLD.right_key THEN left_key + _skew_edit ELSE CASE WHEN left_key> OLD.right_key THEN left_key - _skew_tree ELSE left_key END END, "level" = CASE WHEN right_key <= OLD.right_key THEN "level" + _skew_level ELSE "level" END, right_key = CASE WHEN right_key <= OLD.right_key THEN right_key + _skew_edit ELSE CASE WHEN right_key <_left_key THEN right_key - _skew_tree ELSE right_key END END, _trigger_lock_update = TRUE WHERE tree = OLD.tree AND right_key> OLD.left_key AND left_key <_left_key AND id <> OLD.id; _left_key: = _left_key - _skew_tree; ELSE - Move down the tree: _skew_edit: = _left_key - OLD.left_key; UPDATE ns_tree SET right_key = CASE WHEN left_key> = OLD.left_key THEN right_key + _skew_edit ELSE CASE WHEN right_key <OLD.left_key THEN right_key + _skew_tree ELSE right_key END END, "level" = CASE WHEN left_key> = OLD.left_key THEN "level" + _skew_level ELSE "level" END, left_key = CASE WHEN left_key> = OLD.left_key THEN left_key + _skew_edit ELSE CASE WHEN left_key> = _left_key THEN left_key + _skew_tree ELSE left_key END END, _trigger_lock_update = TRUE WHERE tree = OLD.tree AND right_key> = _left_key AND left_key <OLD.right_key AND id <> OLD.id; END IF; - Tree rebuilt, only our current node remains NEW.left_key: = _left_key; NEW. "Level": = _level; NEW.right_key: = _left_key + _skew_tree - 1; RETURN NEW; END; $ BODY $ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION ns_tree_before_update_func () OWNER TO user; CREATE TRIGGER ns_tree_before_update_tr BEFORE UPDATE ON ns_tree FOR EACH ROW EXECUTE PROCEDURE ns_tree_before_update_func ();Explanations:
CREATE OR REPLACE FUNCTION ns_tree_after_delete_func () RETURNS trigger AS $ BODY $ DECLARE _skew_tree INTEGER; BEGIN PERFORM lock_ns_tree (OLD.tree); - Check whether the trigger should be executed: IF OLD._trigger_for_delete = TRUE THEN RETURN OLD; END IF; - We mark on deleting child nodes: UPDATE ns_tree SET _trigger_for_delete = TRUE, _trigger_lock_update = TRUE WHERE tree = OLD.tree AND left_key> OLD.left_key AND right_key <OLD.right_key; - Remove marked nodes: DELETE FROM ns_tree WHERE tree = OLD.tree AND left_key> OLD.left_key AND right_key <OLD.right_key; - Remove the gap in the keys: _skew_tree: = OLD.right_key - OLD.left_key + 1; UPDATE ns_tree SET left_key = CASE WHEN left_key> OLD.left_key THEN left_key - _skew_tree ELSE left_key END, right_key = right_key - _skew_tree, _trigger_lock_update = TRUE WHERE right_key> OLD.right_key AND tree = OLD.tree; RETURN OLD; END; $ BODY $ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION ns_tree_after_delete_func () OWNER TO user; CREATE TRIGGER ns_tree_after_delete_tr AFTER DELETE ON ns_tree FOR EACH ROW EXECUTE PROCEDURE ns_tree_after_delete_func ();In the second solution, we simply move the child tree one level up, and remove the key gap. SQL code (6)
CREATE OR REPLACE FUNCTION ns_tree_after_delete_2_func () RETURNS trigger AS $ BODY $ DECLARE BEGIN PERFORM lock_ns_tree (OLD.tree); - Remove the gap in the keys and shift the child nodes: UPDATE ns_tree SET left_key = CASE WHEN left_key <OLD.left_key THEN left_key ELSE CASE WHEN right_key <OLD.right_key THEN left_key - 1 ELSE left_key - 2 END END, "level" = CASE WHEN right_key <OLD.right_key THEN "level" - 1 ELSE "level" END, parent_id = CASE WHEN right_key <OLD.right_key AND "level" = OLD.level + 1 THEN OLD.parent_id ELSE parent_id END, right_key = CASE WHEN right_key <OLD.right_key THEN right_key - 1 ELSE right_key - 2 END, _trigger_lock_update = TRUE WHERE (right_key> OLD.right_key OR (left_key> OLD.left_key AND right_key <OLD.right_key)) AND tree = OLD.tree; RETURN OLD; END; $ BODY $ LANGUAGE 'plpgsql' VOLATILE COST 100; ALTER FUNCTION ns_tree_after_delete_2_func () OWNER TO user; CREATE TRIGGER ns_tree_after_delete_2_tr AFTER DELETE ON ns_tree FOR EACH ROW EXECUTE PROCEDURE ns_tree_after_delete_2_func ();Actually everything. It remains only to put down the indices (I'm lazy to write SQL commands here, so I’ll just voice them):
Source: https://habr.com/ru/post/63416/
All Articles