← hierarchy-data-closure-table
first commit
+189 · 7 files changed
-
sql/p_prefix_nodes_add_new_paths_after_insert.sql+34 −0 -
sql/p_prefix_nodes_get_subtree_by_node_id.sql+35 −0 -
sql/p_prefix_nodes_move_old_paths_after_update.sql+49 −0 -
sql/prefix_nodes_add_new_paths_after_insert.sql+15 −0 -
sql/prefix_nodes_move_old_paths_after_update.sql+15 −0 -
sql/sample_data.sql+17 −0 -
sql/tables.sql+24 −0
@@ -0,0 +1,34 @@ +DELIMITER $$ + +USE `_category`$$ + +DROP PROCEDURE IF EXISTS `p_prefix_nodes_add_new_paths_after_insert`$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `p_prefix_nodes_add_new_paths_after_insert`( + param_node_new_id INT UNSIGNED, + param_node_parent_id INT UNSIGNED +) + COMMENT 'Adding new paths information to prefix_nodes_paths table' +BEGIN + -- Update paths information + INSERT INTO `prefix_nodes_paths` ( + `ancestor_id`, + `descendant_id`, + `path_length` + ) + SELECT + `ancestor_id`, + `param_node_new_id`, + `path_length` + 1 + FROM + `prefix_nodes_paths` + WHERE `descendant_id` = `param_node_parent_id` + UNION + ALL + SELECT + `param_node_new_id`, + `param_node_new_id`, + 0 ; +END$$ + +DELIMITER ; @@ -0,0 +1,35 @@ +DELIMITER $$ + +USE `_category`$$ + +DROP PROCEDURE IF EXISTS `p_prefix_nodes_get_subtree_by_node_id`$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `p_prefix_nodes_get_subtree_by_node_id`( + node_id INT UNSIGNED +) COMMENT 'Query all descendants nodes by a node id, return as a result set' +BEGIN + SELECT + d.`id`, + d.`is_deleted`, + d.`parent_id`, + CONCAT( + REPEAT('-', p.`path_length`), + d.`name` + ) AS tree, + p.`path_length`, + GROUP_CONCAT( + crumbs.`ancestor_id` SEPARATOR ',' + ) AS breadcrumbs + FROM + `prefix_nodes` AS d + JOIN `prefix_nodes_paths` AS p + ON d.`id` = p.`descendant_id` + JOIN `prefix_nodes_paths` AS crumbs + ON crumbs.`descendant_id` = p.`descendant_id` + WHERE p.`ancestor_id` = `node_id` + AND d.`is_deleted` = 0 + GROUP BY d.`id` + ORDER BY breadcrumbs ; +END$$ + +DELIMITER ; @@ -0,0 +1,49 @@ +DELIMITER $$ + +USE `_category`$$ + +DROP PROCEDURE IF EXISTS `p_prefix_nodes_move_old_paths_after_update`$$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `p_prefix_nodes_move_old_paths_after_update`( + `node_old_parent_id` INT UNSIGNED, + `node_new_parent_id` INT UNSIGNED +) COMMENT 'Update paths when parent_id column changed' +BEGIN + -- References: + -- http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/ + -- The store procedure is used to update paths informations when the value of parent_id columns is changed (when move a node to a new parent) + -- If parent_id has chanaged + -- 1. Delete the paths between moved node and old ancestors + -- 2. Add the paths between moved node and new ancestors + IF `node_old_parent_id` != `node_new_parent_id` + THEN + DELETE + a + FROM + `prefix_nodes_paths` AS a + JOIN `prefix_nodes_paths` AS d + ON a.`descendant_id` = d.`descendant_id` + LEFT JOIN `prefix_nodes_paths` AS x + ON x.`ancestor_id` = d.`ancestor_id` + AND x.`descendant_id` = a.`ancestor_id` + WHERE d.`ancestor_id` = `node_old_parent_id` + AND x.`ancestor_id` IS NULL ; + -- Add the node to its new parent + INSERT `prefix_nodes_paths` ( + `ancestor_id`, + `descendant_id`, + `path_length` + ) + SELECT + supertree.`ancestor_id`, + subtree.`descendant_id`, + supertree.`path_length` + subtree.`path_length` + 1 + FROM + `prefix_nodes_paths` AS supertree + JOIN `prefix_nodes_paths` AS subtree + WHERE subtree.`ancestor_id` = `node_old_parent_id` + AND supertree.`descendant_id` = `node_new_parent_id` ; + END IF ; +END$$ + +DELIMITER ; @@ -0,0 +1,15 @@ +DELIMITER $$ + +USE `_category`$$ + +DROP TRIGGER /*!50032 IF EXISTS */ `prefix_nodes_add_new_paths_after_insert`$$ + +CREATE + /*!50017 DEFINER = 'root'@'localhost' */ + TRIGGER `prefix_nodes_add_new_paths_after_insert` AFTER INSERT ON `prefix_nodes` + FOR EACH ROW BEGIN + CALL p_prefix_nodes_add_new_paths_after_insert(NEW.`id`, NEW.`parent_id`); +END; +$$ + +DELIMITER ; @@ -0,0 +1,15 @@ +DELIMITER $$ + +USE `_category`$$ + +DROP TRIGGER /*!50032 IF EXISTS */ `prefix_nodes_move_old_paths_after_update`$$ + +CREATE + /*!50017 DEFINER = 'root'@'localhost' */ + TRIGGER `prefix_nodes_move_old_paths_after_update` AFTER UPDATE ON `prefix_nodes` + FOR EACH ROW BEGIN + CALL p_prefix_nodes_move_old_paths_after_update(OLD.`parent_id`, NEW.`parent_id`); +END; +$$ + +DELIMITER ; \ No newline at end of file @@ -0,0 +1,17 @@ +INSERT INTO `prefix_nodes` VALUES(1, NULL, NULL, 'HOME',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(2, 1, 1, 'PRODUCT',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(3, 1, 2, 'CONTACT',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(4, 1, 3, 'DOCUMENTATION',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(5, 2, 2, 'SOFTWARE',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(6, 2, 1, 'HARDWARE',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(7, 1, 3, 'DEMO',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(8, 4, 1, 'JAVA',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(9, 4, 2, 'PHP',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(10, NULL, NULL, 'Asia',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(11, 10, 1, 'China',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(12, 10, 2, 'Korea',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(13, 10, 3, 'Japan',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(14, 6, 3, 'CPU',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(15, 6, 4, 'HARD DISK',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(16, 15, 1, 'SSD',DEFAULT); +INSERT INTO `prefix_nodes` VALUES(17, 15, 2, 'HDD',DEFAULT); @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS `prefix_nodes_paths`; +DROP TABLE IF EXISTS `prefix_nodes`; + +CREATE TABLE `prefix_nodes` ( + `id` int(11) NOT NULL, + `parent_id` int(11) DEFAULT NULL, + `order` int(11) DEFAULT NULL, + `name` varchar(128) NOT NULL, + `is_deleted` tinyint(1) DEFAULT '0', + PRIMARY KEY (`id`), + KEY `parent_id` (`parent_id`), + KEY `name` (`name`), + CONSTRAINT `prefix_nodes_ibfk1` FOREIGN KEY (`parent_id`) REFERENCES `prefix_nodes` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `prefix_nodes_paths` ( + `ancestor_id` int(11) NOT NULL, + `descendant_id` int(11) NOT NULL, + `path_length` int(11) NOT NULL, + PRIMARY KEY (`ancestor_id`,`descendant_id`), + KEY `descendant_id` (`descendant_id`), + CONSTRAINT `prefix_nodes_paths_ibfk_1` FOREIGN KEY (`ancestor_id`) REFERENCES `prefix_nodes` (`id`) ON DELETE CASCADE, + CONSTRAINT `prefix_nodes_paths_ibfk_2` FOREIGN KEY (`descendant_id`) REFERENCES `prefix_nodes` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8;