← hierarchy-data-closure-table

first commit

dcc158da44dd5f5443fdf997d3e2a61bb9080e15 · developerworks · May 14, 2013 14:39

+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
diff --git a/sql/p_prefix_nodes_add_new_paths_after_insert.sql b/sql/p_prefix_nodes_add_new_paths_after_insert.sql
new file mode 100644
index 0000000..48fb882
--- /dev/null
+++ b/sql/p_prefix_nodes_add_new_paths_after_insert.sql
@@ -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 ;
diff --git a/sql/p_prefix_nodes_get_subtree_by_node_id.sql b/sql/p_prefix_nodes_get_subtree_by_node_id.sql
new file mode 100644
index 0000000..bca6494
--- /dev/null
+++ b/sql/p_prefix_nodes_get_subtree_by_node_id.sql
@@ -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 ;
diff --git a/sql/p_prefix_nodes_move_old_paths_after_update.sql b/sql/p_prefix_nodes_move_old_paths_after_update.sql
new file mode 100644
index 0000000..e333b9a
--- /dev/null
+++ b/sql/p_prefix_nodes_move_old_paths_after_update.sql
@@ -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 ;
diff --git a/sql/prefix_nodes_add_new_paths_after_insert.sql b/sql/prefix_nodes_add_new_paths_after_insert.sql
new file mode 100644
index 0000000..99cddf9
--- /dev/null
+++ b/sql/prefix_nodes_add_new_paths_after_insert.sql
@@ -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 ;
diff --git a/sql/prefix_nodes_move_old_paths_after_update.sql b/sql/prefix_nodes_move_old_paths_after_update.sql
new file mode 100644
index 0000000..c51e9a5
--- /dev/null
+++ b/sql/prefix_nodes_move_old_paths_after_update.sql
@@ -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
diff --git a/sql/sample_data.sql b/sql/sample_data.sql
new file mode 100644
index 0000000..c8220cb
--- /dev/null
+++ b/sql/sample_data.sql
@@ -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);
diff --git a/sql/tables.sql b/sql/tables.sql
new file mode 100644
index 0000000..f164387
--- /dev/null
+++ b/sql/tables.sql
@@ -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;