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