← hierarchy-data-closure-table

refactor directory structure

e1ae02bcbc3ccdfedd7449fabdb550b07950debc · developerworks · Oct 9, 2017 18:59

+237 −2 · 13 files changed

  • mysql/all.sql +230 −0
  • mysql/create_tables.sql +4 −1
  • postgres/README.md +3 −1
  • postgres/{table.sql => all-in-one.sql} +0 −0
  • postgres/{postgresql_create_tables.sql => create_table_structure.sql} +0 −0
  • postgres/{ => data}/categories_data.sql +0 −0
  • postgres/{ => procedures}/add_node.sql +0 −0
  • postgres/{ => procedures}/delete_subtree.sql +0 −0
  • postgres/{ => procedures}/get_child_nodes.sql +0 −0
  • postgres/{ => procedures}/get_subtree.sql +0 −0
  • postgres/{ => procedures}/json_output.sql +0 −0
  • postgres/{ => procedures}/move_subtree.sql +0 −0
  • postgres/{ => triggers}/add_new_paths_for_insert.sql +0 −0
diff --git a/mysql/all.sql b/mysql/all.sql
new file mode 100644
index 0000000..83c46d1
--- /dev/null
+++ b/mysql/all.sql
@@ -0,0 +1,230 @@
+DROP TABLE IF EXISTS `prefix_nodes_paths`;
+DROP TABLE IF EXISTS `prefix_nodes`;
+
+CREATE TABLE `prefix_nodes` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `order` int(11) DEFAULT NULL,
+  `name` varchar(128) NOT NULL,
+  `is_deleted` tinyint(1) DEFAULT '0',
+  `user_id` int(11) NOT NULL,
+  `user_type` int(11) NOT NULL,
+  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;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP TRIGGER IF EXISTS `trigger_add_node`$$
+
+CREATE
+    TRIGGER `trigger_add_node` AFTER INSERT ON `prefix_nodes`
+    FOR EACH ROW BEGIN
+    CALL p_node_add(NEW.`id`, NEW.`parent_id`);
+END;
+$$
+
+DELIMITER ;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP TRIGGER IF EXISTS `trigger_move_node`$$
+
+CREATE TRIGGER `trigger_move_node` AFTER UPDATE ON `prefix_nodes`
+  FOR EACH ROW BEGIN
+  IF OLD.`parent_id` != NEW.`parent_id` THEN
+    -- http://www.mysqlperformanceblog.com/2011/02/14/moving-subtrees-in-closure-table/
+    -- in the example, when change node D's parent to B.
+    -- its sql has only D & B. so I think it should currentNode.id& newParent.id
+    CALL p_node_move(NEW.`id`, NEW.`parent_id`);
+  END IF;
+
+  IF OLD.`is_deleted` != NEW.`is_deleted` THEN
+      CALL p_node_hide(NEW.`parent_id`, NEW.`is_deleted`);
+  END IF;
+END;
+$$
+
+DELIMITER ;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP PROCEDURE IF EXISTS `p_get_tree`$$
+
+CREATE PROCEDURE `p_get_tree`(
+    node_id INT UNSIGNED
+) COMMENT 'Query all descendants nodes by a node id, return as a result set'
+BEGIN
+  SELECT
+    node.`id`,
+    node.`is_deleted`,
+    node.`parent_id`,
+    CONCAT(
+        REPEAT('-', path.`path_length`),
+        node.`name`
+    ) AS name,
+    path.`path_length`,
+    GROUP_CONCAT(
+        crumbs.`ancestor_id` SEPARATOR ','
+    ) AS breadcrumbs
+  FROM
+    `prefix_nodes` AS node
+    JOIN `prefix_nodes_paths` AS path
+      ON node.`id` = path.`descendant_id`
+    JOIN `prefix_nodes_paths` AS crumbs
+      ON crumbs.`descendant_id` = path.`descendant_id`
+  WHERE path.`ancestor_id` = `node_id`
+    AND node.`is_deleted` = 0
+  GROUP BY node.`id`
+  ORDER BY breadcrumbs ;
+END$$
+
+DELIMITER ;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP PROCEDURE IF EXISTS `p_node_add`$$
+
+CREATE PROCEDURE `p_node_add`(
+  param_node_new_id    INT UNSIGNED,
+  param_node_parent_id INT UNSIGNED
+)
+COMMENT 'Adding new paths 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 ;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP PROCEDURE IF EXISTS `p_node_hide`$$
+
+CREATE PROCEDURE `p_node_hide` (
+  `node_id` INT UNSIGNED,
+  `deleted` INT UNSIGNED
+) COMMENT 'Delete a node and its descendant nodes(update is_deleted = 1)'
+BEGIN
+  UPDATE
+    `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` SET d.`is_deleted` = deleted
+  WHERE p.`ancestor_id` = node_id;
+END $$
+
+DELIMITER ;
+
+
+DELIMITER $$
+
+USE `hierarchy_data`$$
+
+DROP PROCEDURE IF EXISTS `p_node_move`$$
+
+CREATE PROCEDURE `p_node_move` (
+    `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
+  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$$
+
+DELIMITER ;
+
+
+INSERT INTO `prefix_nodes` VALUES(1, NULL, NULL, 'ROOT',DEFAULT, 0, 0);
+INSERT INTO `prefix_nodes` VALUES(2, 1, NULL, 'C0',DEFAULT, 0, 3);
+INSERT INTO `prefix_nodes` VALUES(3, 1, NULL, 'B0',DEFAULT, 0, 2);
+
+
+
+
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A1',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A2',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A3',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A4',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A5',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A6',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A7',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A8',DEFAULT, 0, 1);
+INSERT INTO `prefix_nodes` VALUES(NULL, 3, NULL, 'A9',DEFAULT, 0, 1);
+
diff --git a/mysql/create_tables.sql b/mysql/create_tables.sql
index f164387..7b05220 100644
--- a/mysql/create_tables.sql
+++ b/mysql/create_tables.sql
@@ -2,17 +2,20 @@ DROP TABLE IF EXISTS `prefix_nodes_paths`;
 DROP TABLE IF EXISTS `prefix_nodes`;
 
 CREATE TABLE `prefix_nodes` (
-  `id` int(11) NOT NULL,
+  `id` int(11) NOT NULL AUTO_INCREMENT,
   `parent_id` int(11) DEFAULT NULL,
   `order` int(11) DEFAULT NULL,
   `name` varchar(128) NOT NULL,
   `is_deleted` tinyint(1) DEFAULT '0',
+  `user_id` int(11) NOT NULL,
+  `user_type` int(11) NOT NULL,
   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,
diff --git a/postgres/README.md b/postgres/README.md
index 0376e04..4901208 100644
--- a/postgres/README.md
+++ b/postgres/README.md
@@ -1,4 +1,6 @@
-##
+## Description
+
+
 
 
 ## References
diff --git a/postgres/table.sql b/postgres/all-in-one.sql
similarity index 100%
rename from postgres/table.sql
rename to postgres/all-in-one.sql
diff --git a/postgres/postgresql_create_tables.sql b/postgres/create_table_structure.sql
similarity index 100%
rename from postgres/postgresql_create_tables.sql
rename to postgres/create_table_structure.sql
diff --git a/postgres/categories_data.sql b/postgres/data/categories_data.sql
similarity index 100%
rename from postgres/categories_data.sql
rename to postgres/data/categories_data.sql
diff --git a/postgres/add_node.sql b/postgres/procedures/add_node.sql
similarity index 100%
rename from postgres/add_node.sql
rename to postgres/procedures/add_node.sql
diff --git a/postgres/delete_subtree.sql b/postgres/procedures/delete_subtree.sql
similarity index 100%
rename from postgres/delete_subtree.sql
rename to postgres/procedures/delete_subtree.sql
diff --git a/postgres/get_child_nodes.sql b/postgres/procedures/get_child_nodes.sql
similarity index 100%
rename from postgres/get_child_nodes.sql
rename to postgres/procedures/get_child_nodes.sql
diff --git a/postgres/get_subtree.sql b/postgres/procedures/get_subtree.sql
similarity index 100%
rename from postgres/get_subtree.sql
rename to postgres/procedures/get_subtree.sql
diff --git a/postgres/json_output.sql b/postgres/procedures/json_output.sql
similarity index 100%
rename from postgres/json_output.sql
rename to postgres/procedures/json_output.sql
diff --git a/postgres/move_subtree.sql b/postgres/procedures/move_subtree.sql
similarity index 100%
rename from postgres/move_subtree.sql
rename to postgres/procedures/move_subtree.sql
diff --git a/postgres/add_new_paths_for_insert.sql b/postgres/triggers/add_new_paths_for_insert.sql
similarity index 100%
rename from postgres/add_new_paths_for_insert.sql
rename to postgres/triggers/add_new_paths_for_insert.sql