← hierarchy-data-closure-table

Refactors

b661fa44b42a9f7b26c12a806c02851cd0cdfbaf · developerworks · May 20, 2017 23:12

+881 −233 · 28 files changed

  • .gitignore +1 −0
  • README.md +36 −35
  • sql/tables.sql => mysql/create_tables.sql +0 −0
  • mysql/p_get_tree.sql +35 −0
  • mysql/p_node_add.sql +34 −0
  • mysql/p_node_hide.sql +21 −0
  • mysql/p_node_move.sql +47 −0
  • {sql => mysql}/sample_data.sql +0 −0
  • mysql/trigger_add_node.sql +14 −0
  • mysql/trigger_move_node.sql +22 −0
  • postgres/README.md +7 −0
  • postgres/add_new_paths_for_insert.sql +65 −0
  • postgres/add_node.sql +34 −0
  • postgres/categories_data.sql +65 −0
  • postgres/delete_subtree.sql +20 −0
  • postgres/get_child_nodes.sql +26 −0
  • postgres/get_subtree.sql +40 −0
  • postgres/json_output.sql +64 −0
  • postgres/move_subtree.sql +31 −0
  • postgres/postgresql_create_tables.sql +75 −0
  • postgres/table.sql +244 −0
  • sql/p_prefix_nodes_add_new_paths_after_insert.sql +0 −34
  • sql/p_prefix_nodes_delete_nodes.sql +0 −22
  • sql/p_prefix_nodes_delete_nodes_after_update.sql +0 −22
  • sql/p_prefix_nodes_get_subtree_by_node_id.sql +0 −35
  • sql/p_prefix_nodes_move_old_paths_after_update.sql +0 −46
  • sql/prefix_nodes_add_new_paths_after_insert.sql +0 −15
  • sql/prefix_nodes_move_old_paths_after_update.sql +0 −24
diff --git a/.gitignore b/.gitignore
index e69de29..960dd8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+.test
diff --git a/README.md b/README.md
index acd8788..f61292b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
-Closure Table
-=============
+# Closure Table
 
-This is a mysql store procedure and trigger implementation of closure table in RDBMS about hierarchy data model.
+
+This is a mysql store procedure and trigger implementation of closure table in
+RDBMS about hierarchy data model.
 
 <h3>Closure Table Node Paths</h3>
 
@@ -11,60 +12,60 @@ This is a mysql store procedure and trigger implementation of closure table in R
 
 ![Query the subtree nodes](https://raw.github.com/developerworks/hierarchy-data-closure-table/master/assets/call%20p_prefix_nodes_get_subtree_by_node_id.png "Query the subtree nodes")
 
+## Features
+
+
+* Automatically add new paths when you insert a new node
+
+* Automatically update(`DELETE` old paths and `INSERT` new paths) paths when you
+update `parent_id` of a node. (This means move a node/subtree to a new parent)
+
+* A store procedure that is used to select a whole subtree by a `node_id`
+(if the `node_id` has descendant)
 
-Features
---------
 
-* Automatically add new paths relationship information between ancester and descendant nodes when you insert node to table `prefix_nodes`
-* Automatically update(`DELETE` old paths and `INSERT` new paths) paths relationship information when you update the `parent_id` column of `prefix_nodes` table if the new value parent_id if different(This means move a node/subtree to a new parent node)
-* A store procedure that is used to select a whole subtree by a `node_id` (if the `node_id` has descendant)
+## Triggers
 
 
+* `trigger_add_paths`
 
-Triggers
---------
+The trigger is execute when insert a node into `prefix_nodes` table, and call `p_node_add` to add update paths.
 
-* `prefix_nodes_add_new_paths_after_insert`
-The trigger is execute when insert a node into `prefix_nodes` table, and call `p_prefix_nodes_add_new_paths_after_insert` to add new paths info.
+* `prefix_node_move`:
 
-* `prefix_nodes_move_old_paths_after_update`:
-The trigger is execute when update the `parent_id` column of `prefix_nodes` table only if `OLD.parent_id != NEW.parent_id`
+The trigger is execute when update the `parent_id` column of `prefix_nodes`
+table only if `OLD.parent_id != NEW.parent_id`
 
-Store Procedures
-----------------
+## Store Procedures
 
-It's very clear of means just like the name of the procedures.
 
-* `p_prefix_nodes_add_new_paths_after_insert(param_node_new_id INT UNSIGNED,param_node_parent_id INT UNSIGNED)`
+* `p_node_add(param_node_new_id INT UNSIGNED,param_node_parent_id INT UNSIGNED)`
 
   Add new paths when insert a node to `prefix_nodes` table
-  
-* `p_prefix_nodes_get_subtree_by_node_id(node_id INT UNSIGNED)`
+
+* `p_get_tree(node_id INT UNSIGNED)`
 
   Get subtree by a node id
-  
-* `p_prefix_nodes_move_old_paths_after_update(node_old_parent_id INT UNSIGNED,node_new_parent_id INT UNSIGNED)`
+
+* `p_node_move(node_old_parent_id INT UNSIGNED,node_new_parent_id INT UNSIGNED)`
 
   Update paths when move a node to a new parent node
-  
-* `p_prefix_nodes_delete_nodes(node_id INT UNSIGNED, is_deleted INT UNSIGNED)`
 
-  Hidden or show nodes from subtree, explains as following:
+* `p_node_hide(node_id INT UNSIGNED, is_deleted INT UNSIGNED)`
 
-  - First `call p_prefix_nodes_get_subtree_by_node_id(6)` get a HARDWARE subtree,
-  - Second `call p_prefix_nodes_delete_nodes(6, 0)` to hidden subtree,
-  - Third `call p_prefix_nodes_get_subtree_by_node_id(6)` again get HARDWARE subtree, but this time the HARDWARE subtree was disappeared
-  - Fourth `call p_prefix_nodes_delete_nodes(6, 1)` show HARDWARE subtree
+  Hide or show nodes from subtree, explains as following:
 
-Files
------
+  - Step 1. `call p_get_tree(6)` get the `HARDWARE` subtree,
+  - Step 2. `call p_node_hide(6, 0)` to hide a subtree,
+  - Step 3. `call p_get_tree(6)` get the `HARDWARE` subtree, when you get a subtree, it is not show in the result.
+  - Step 4. `call p_node_hide(6, 1)` show `HARDWARE` subtree
 
-* `sql/tables.sql`
+## Files
+
+* `./sql/tables.sql`
 
   Create tables.
 
-* `sql/sample_data.sql`
+* `./sql/sample_data.sql`
 
   Some insert statements for testing
-  
-
diff --git a/sql/tables.sql b/mysql/create_tables.sql
similarity index 100%
rename from sql/tables.sql
rename to mysql/create_tables.sql
diff --git a/mysql/p_get_tree.sql b/mysql/p_get_tree.sql
new file mode 100644
index 0000000..2170839
--- /dev/null
+++ b/mysql/p_get_tree.sql
@@ -0,0 +1,35 @@
+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
+    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/mysql/p_node_add.sql b/mysql/p_node_add.sql
new file mode 100644
index 0000000..be4c630
--- /dev/null
+++ b/mysql/p_node_add.sql
@@ -0,0 +1,34 @@
+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 ;
diff --git a/mysql/p_node_hide.sql b/mysql/p_node_hide.sql
new file mode 100644
index 0000000..acf0404
--- /dev/null
+++ b/mysql/p_node_hide.sql
@@ -0,0 +1,21 @@
+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 ;
diff --git a/mysql/p_node_move.sql b/mysql/p_node_move.sql
new file mode 100644
index 0000000..593d7bf
--- /dev/null
+++ b/mysql/p_node_move.sql
@@ -0,0 +1,47 @@
+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 ;
diff --git a/sql/sample_data.sql b/mysql/sample_data.sql
similarity index 100%
rename from sql/sample_data.sql
rename to mysql/sample_data.sql
diff --git a/mysql/trigger_add_node.sql b/mysql/trigger_add_node.sql
new file mode 100644
index 0000000..e51c925
--- /dev/null
+++ b/mysql/trigger_add_node.sql
@@ -0,0 +1,14 @@
+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 ;
diff --git a/mysql/trigger_move_node.sql b/mysql/trigger_move_node.sql
new file mode 100644
index 0000000..abbbbac
--- /dev/null
+++ b/mysql/trigger_move_node.sql
@@ -0,0 +1,22 @@
+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 ;
diff --git a/postgres/README.md b/postgres/README.md
new file mode 100644
index 0000000..0376e04
--- /dev/null
+++ b/postgres/README.md
@@ -0,0 +1,7 @@
+##
+
+
+## References
+
+
+[Building a nested hierarchy of JSON in a relational DB](https://lakshminp.com/building-nested-hierarchy-json-relational-db)
diff --git a/postgres/add_new_paths_for_insert.sql b/postgres/add_new_paths_for_insert.sql
new file mode 100644
index 0000000..b0902d4
--- /dev/null
+++ b/postgres/add_new_paths_for_insert.sql
@@ -0,0 +1,65 @@
+-----------------------------------------------
+-- This function is used to update paths when
+-- inserted a new node to a exist parent node.
+-----------------------------------------------
+
+-- Drop function
+DROP FUNCTION IF EXISTS after_change_node();
+
+-- Step 1: Create a function used to update paths
+CREATE OR REPLACE FUNCTION after_change_node() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
+BEGIN
+  IF (TG_OP = 'INSERT') THEN
+    INSERT INTO prefix_nodes_paths(ancestor_id,descendant_id,depth)
+    SELECT ancestor_id, NEW.id, depth + 1
+    FROM prefix_nodes_paths WHERE descendant_id = NEW.parent_id
+    UNION ALL SELECT NEW.id, NEW.id, 0;
+  ELSE IF (TG_OP = 'UPDATE') THEN
+    -- Move Subtree
+    -- References:
+    -- https://codegists.com/snippet/sql/closure_treesql_x2002uwh_sql
+    IF OLD.parent_id != NEW.parent_id THEN
+      -- Step 1: Disconnect from current ancestors
+      DELETE FROM prefix_nodes_paths
+      WHERE descendant_id IN (SELECT descendant_id FROM prefix_nodes_paths WHERE ancestor_id = OLD.parent_id)
+          AND ancestor_id IN (SELECT ancestor_id   FROM prefix_nodes_paths WHERE descendant_id = OLD.parent_id AND ancestor_id != descendant_id);
+
+      -- Step 2: Insert rows matching ancestors of insertion point and descendants of subtree
+      INSERT INTO prefix_nodes_paths (ancestor, descendant)
+        SELECT supertree.ancestor, subtree.descendant
+        FROM prefix_nodes_paths AS supertree
+        CROSS JOIN prefix_nodes_paths AS subtree
+        WHERE supertree.descendant = 3
+        AND subtree.ancestor = 6;
+
+
+      -- Add new paths
+      INSERT prefix_nodes_paths (ancestor_id, descendant_id, depth)
+        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 = OLD.parent_id
+        AND supertree.descendant_id = NEW.parent_id ;
+
+    END IF;
+  END IF;
+  RETURN NULL;
+END;
+$$;
+
+
+-- Add trigger function on prefix_nodes table
+CREATE TRIGGER after_change_node AFTER INSERT OR UPDATE
+  ON prefix_nodes FOR EACH ROW EXECUTE PROCEDURE after_change_node();
+
+--
+-- If you want modify the `after_change_node` function,
+-- you must delete the trigger from `prefix_nodes` table
+-- first, and re-create it.
+--
+
+-- Drop trigger function on a table
+DROP TRIGGER IF EXISTS after_change_node ON prefix_nodes;
+
+
+
diff --git a/postgres/add_node.sql b/postgres/add_node.sql
new file mode 100644
index 0000000..cab689a
--- /dev/null
+++ b/postgres/add_node.sql
@@ -0,0 +1,34 @@
+-----------------------------------------------
+-- Add a new node to existing parent node.
+--
+-- If you want modify the `after_change_node` function,
+-- you must delete the trigger from `prefix_nodes` table
+-- first, and re-create it.
+--
+-----------------------------------------------
+
+-- Drop trigger on a table
+DROP TRIGGER IF EXISTS after_change_node ON prefix_nodes;
+
+-- Drop trigger function
+DROP FUNCTION IF EXISTS after_change_node();
+
+-- Step 1: Create a function used to update paths
+CREATE OR REPLACE FUNCTION after_change_node() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
+BEGIN
+  IF (TG_OP = 'INSERT') THEN
+    INSERT INTO prefix_nodes_paths(ancestor_id,descendant_id,depth)
+    SELECT ancestor_id, NEW.id, depth + 1
+    FROM prefix_nodes_paths WHERE descendant_id = NEW.parent_id
+    UNION ALL SELECT NEW.id, NEW.id, 0;
+  END IF;
+  RETURN NULL;
+END;
+$$;
+
+-- Add trigger function on prefix_nodes table
+CREATE TRIGGER after_change_node AFTER INSERT OR UPDATE
+  ON prefix_nodes FOR EACH ROW EXECUTE PROCEDURE after_change_node();
+
+
+
diff --git a/postgres/categories_data.sql b/postgres/categories_data.sql
new file mode 100644
index 0000000..ec6cf4a
--- /dev/null
+++ b/postgres/categories_data.sql
@@ -0,0 +1,65 @@
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'HOME', false, NULL);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'PRODUCT', false, 1);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'CONTACT', false, 1);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'DOCUMENTATION', false, 1);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'SOFTWARE', false, 2);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'HARDWARE', false, 2);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'DEMO', false, 1);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'JAVA', false, 4);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'PHP', false, 4);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Asia', false, NULL);
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'China', false, 10);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Korea', false, 10);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Japan', false, 10);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'CPU', false, 6);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'HARD DISK', false, 6);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'SSD', false, 15);
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'HDD', false, 15);
+
+
+
+INSERT INTO prefix_nodes(id, "order", name, is_deleted, parent_id) VALUES('29f9d3a1-068f-4132-bf5e-4792add8489b', 0, 'Subtree Test', false, NULL);
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Child Node 1', false, '29f9d3a1-068f-4132-bf5e-4792add8489b');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Child Node 2', false, '29f9d3a1-068f-4132-bf5e-4792add8489b');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Child Node 3', false, '29f9d3a1-068f-4132-bf5e-4792add8489b');
+
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Child Node 3 - Child', false, '72850f82-7dab-45d9-8560-dc183abf3817');
+
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, 'Child Node 3 - Child', false, '5efc1e96-6931-4310-ac09-ad791f22d7e3');
+
+
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '春季焕新', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '商场同款', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '修身夹克', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '牛仔夹克', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '潮流卫衣', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '长袖衬衫', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '长袖T恤', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '针织开衫', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '单西', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '束脚裤', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '水洗牛仔裤', false, 'e632b593-8b1b-4966-8427-3273f2d43f4c');
+
+
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '精选上装', false, 'a498140e-be81-494f-926f-b907614ac3fe');
+
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '羽绒服', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '毛呢外套', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '毛衣', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '针织衫', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '衬衫', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '风衣', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '短外套', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '卫衣', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+INSERT INTO prefix_nodes("order", name, is_deleted, parent_id) VALUES(0, '小西装', false, 'e462c5b8-3674-4a06-9abb-047e9cb4a0d8');
+
+
+
+
diff --git a/postgres/delete_subtree.sql b/postgres/delete_subtree.sql
new file mode 100644
index 0000000..05adf3d
--- /dev/null
+++ b/postgres/delete_subtree.sql
@@ -0,0 +1,20 @@
+--------------------------------------------
+-- Delete a leaf
+--------------------------------------------
+
+
+
+--------------------------------------------
+-- Delete a subtree
+--------------------------------------------
+
+DROP FUNCTION IF EXISTS delete_subtree(INTEGER);
+
+CREATE FUNCTION delete_subtree(INTEGER)
+RETURNS VOID
+AS $$
+  DELETE FROM prefix_nodes_paths
+  WHERE descendant_id IN (
+    SELECT descendant_id FROM prefix_nodes_paths WHERE ancestor_id = $1
+  );
+$$ LANGUAGE sql
diff --git a/postgres/get_child_nodes.sql b/postgres/get_child_nodes.sql
new file mode 100644
index 0000000..c99371f
--- /dev/null
+++ b/postgres/get_child_nodes.sql
@@ -0,0 +1,26 @@
+----------------
+-- Get child nodes
+----------------
+
+DROP FUNCTION IF EXISTS get_child_nodes(INTEGER);
+
+-- Create function
+CREATE FUNCTION get_child_nodes(INTEGER)
+  RETURNS SETOF RECORD
+  LANGUAGE SQL
+AS $$
+SELECT d.id,
+       d.is_deleted,
+       d.parent_id,
+       concat(repeat('-', p.depth), d.name) AS tree,
+       p.depth,
+       array_to_string(array_agg(crumbs.ancestor_id::CHARACTER VARYING ORDER BY crumbs.ancestor_id),',','*') 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 = $1 AND d.is_deleted = false
+ GROUP BY d.id, p.depth
+ ORDER BY d.id ASC
+ ;
+$$
+
diff --git a/postgres/get_subtree.sql b/postgres/get_subtree.sql
new file mode 100644
index 0000000..e1be2d8
--- /dev/null
+++ b/postgres/get_subtree.sql
@@ -0,0 +1,40 @@
+-----------------------------------------------------------------------
+-- Get child nodes
+-----------------------------------------------------------------------
+
+
+DROP FUNCTION IF EXISTS get_child_nodes(UUID);
+
+-- Create function
+CREATE FUNCTION get_child_nodes(UUID)
+  RETURNS SETOF RECORD
+AS $$
+SELECT d.id,
+       d.is_deleted,
+       d.parent_id,
+       concat(repeat('-', p.depth), d.name) AS name,
+       p.depth,
+       array_to_string(array_agg(crumbs.ancestor_id::CHARACTER VARYING),',','*') 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 = $1 AND d.is_deleted = false
+ GROUP BY d.id, p.depth
+ ORDER BY d.id ASC
+ ;
+$$ LANGUAGE SQL;
+
+
+
+-----------------------------------------------------------------------
+-- Get subtree(Depend on `get_child_nodes` user defined function above)
+-- (include node itself, such as following statement, node #2)
+-- TODO
+-- 1. Add a switch(boolean value) if include node itself
+-----------------------------------------------------------------------
+
+
+SELECT * FROM get_child_nodes('29f9d3a1-068f-4132-bf5e-4792add8489b')
+  AS (id UUID,is_deleted BOOLEAN,parent_id UUID,name VARCHAR(255),depth INTEGER,breadcrumbs VARCHAR(255))
+  ORDER BY breadcrumbs;
+
diff --git a/postgres/json_output.sql b/postgres/json_output.sql
new file mode 100644
index 0000000..9aa5859
--- /dev/null
+++ b/postgres/json_output.sql
@@ -0,0 +1,64 @@
+------------------------------------------
+-- Get tree
+------------------------------------------
+
+DROP FUNCTION IF EXISTS get_tree(data json);
+
+CREATE OR REPLACE FUNCTION get_tree(data json)
+RETURNS json
+AS $$
+var root = [];
+for(var i in data) {
+  build_tree(data[i]['id'], data[i]['name'], data[i]['children']);
+}
+function build_tree(id, name, children) {
+  var exists = getObject(root, id);
+  if(exists) {
+       exists['children'] = children;
+  }
+  else {
+    root.push({'id': id, 'name': name, 'children': children, 'toggled': true});
+  }
+}
+function getObject(theObject, id) {
+  var result = null;
+  if(theObject instanceof Array) {
+    for(var i = 0; i < theObject.length; i++) {
+      result = getObject(theObject[i], id);
+      if (result) {
+        break;
+      }
+    }
+  }
+  else
+  {
+    theObject['toggled'] = true;
+    for(var prop in theObject) {
+      if(prop == 'id') {
+        if(theObject[prop] === id) {
+            return theObject;
+        }
+      }
+      if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
+        result = getObject(theObject[prop], id);
+        if (result) {
+            break;
+        }
+      }
+    }
+  }
+  return result;
+}
+return JSON.stringify(root);
+$$ LANGUAGE plv8 IMMUTABLE STRICT;
+
+------------------------------------------
+-- Format to JSON Object(The Whole Trees)
+------------------------------------------
+
+WITH data AS(
+SELECT array_to_json(array_agg(row_to_json(t))) AS data
+  FROM (
+   SELECT id, name, COALESCE(get_children_by_uuid(id), '[]') AS children, 'true' FROM prefix_nodes
+  ) t
+) SELECT get_tree(data) FROM data;
diff --git a/postgres/move_subtree.sql b/postgres/move_subtree.sql
new file mode 100644
index 0000000..41ab793
--- /dev/null
+++ b/postgres/move_subtree.sql
@@ -0,0 +1,31 @@
+DROP FUNCTION IF EXISTS move(UUID, UUID);
+
+CREATE FUNCTION move(
+  UUID,
+  UUID
+) RETURNS VOID
+AS $$
+--------------------------------------------
+-- Step 1: Update parent node id
+--------------------------------------------
+UPDATE prefix_nodes SET parent_id = $2 WHERE id = $1;
+
+--------------------------------------------
+-- Step 2: Disconnect from current ancestors
+-- Delete all paths that end at descendants in the subtree
+--------------------------------------------
+DELETE FROM prefix_nodes_paths
+WHERE descendant_id IN (SELECT descendant_id FROM prefix_nodes_paths WHERE ancestor_id = $1)
+AND ancestor_id     IN (SELECT ancestor_id   FROM prefix_nodes_paths WHERE descendant_id = $1 AND ancestor_id != descendant_id);
+
+--------------------------------------------
+-- Step 2: Mount subtree to new ancestors
+-- Insert rows matching ancestors of insertion point and descendants of subtree
+--------------------------------------------
+INSERT INTO prefix_nodes_paths (ancestor_id, descendant_id, depth)
+SELECT supertree.ancestor_id, subtree.descendant_id, supertree.depth + subtree.depth + 1
+FROM prefix_nodes_paths AS supertree
+CROSS JOIN prefix_nodes_paths AS subtree
+WHERE supertree.descendant_id = $2
+AND subtree.ancestor_id = $1;
+$$ LANGUAGE SQL;
diff --git a/postgres/postgresql_create_tables.sql b/postgres/postgresql_create_tables.sql
new file mode 100644
index 0000000..705ee69
--- /dev/null
+++ b/postgres/postgresql_create_tables.sql
@@ -0,0 +1,75 @@
+-- Table: public.prefix_nodes
+
+-- DROP TABLE public.prefix_nodes;
+
+CREATE TABLE public.prefix_nodes
+(
+  id integer NOT NULL DEFAULT nextval('prefix_nodes_id_seq'::regclass),
+  parent_id integer,
+  name character varying(255),
+  "order" integer,
+  is_deleted boolean NOT NULL DEFAULT false,
+  CONSTRAINT prefix_nodes_pkey1 PRIMARY KEY (id),
+  CONSTRAINT prefix_nodes_parent_id_fkey FOREIGN KEY (parent_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE
+)
+WITH (
+  OIDS=FALSE
+);
+ALTER TABLE public.prefix_nodes
+  OWNER TO postgres;
+
+-- Index: public.prefix_nodes_parent_id_index
+
+-- DROP INDEX public.prefix_nodes_parent_id_index;
+
+CREATE INDEX prefix_nodes_parent_id_index
+  ON public.prefix_nodes
+  USING btree
+  (parent_id);
+
+
+-- Table: public.prefix_nodes_paths
+
+-- DROP TABLE public.prefix_nodes_paths;
+
+CREATE TABLE public.prefix_nodes_paths
+(
+  id integer NOT NULL DEFAULT nextval('prefix_nodes_paths_id_seq'::regclass),
+  ancestor_id integer,
+  descendant_id integer,
+  depth integer,
+  CONSTRAINT prefix_nodes_paths_pkey PRIMARY KEY (id),
+  CONSTRAINT prefix_nodes_paths_ancestor_id_fkey FOREIGN KEY (ancestor_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE,
+  CONSTRAINT prefix_nodes_paths_descendant_id_fkey FOREIGN KEY (descendant_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE
+)
+WITH (
+  OIDS=FALSE
+);
+ALTER TABLE public.prefix_nodes_paths
+  OWNER TO postgres;
+
+-- Index: public.prefix_nodes_paths_ancestor_id_index
+
+-- DROP INDEX public.prefix_nodes_paths_ancestor_id_index;
+
+CREATE INDEX prefix_nodes_paths_ancestor_id_index
+  ON public.prefix_nodes_paths
+  USING btree
+  (ancestor_id);
+
+-- Index: public.prefix_nodes_paths_descendant_id_index
+
+-- DROP INDEX public.prefix_nodes_paths_descendant_id_index;
+
+CREATE INDEX prefix_nodes_paths_descendant_id_index
+  ON public.prefix_nodes_paths
+  USING btree
+  (descendant_id);
+
+
diff --git a/postgres/table.sql b/postgres/table.sql
new file mode 100644
index 0000000..2e34328
--- /dev/null
+++ b/postgres/table.sql
@@ -0,0 +1,244 @@
+-- Table: public.prefix_nodes
+
+-- DROP TABLE public.prefix_nodes;
+
+CREATE TABLE public.prefix_nodes
+(
+  id uuid NOT NULL DEFAULT gen_random_uuid(),
+  "order" integer,
+  name character varying(255),
+  is_deleted boolean NOT NULL DEFAULT false,
+  parent_id uuid,
+  CONSTRAINT prefix_nodes_pkey PRIMARY KEY (id),
+  CONSTRAINT prefix_nodes_parent_id_fkey FOREIGN KEY (parent_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE
+)
+WITH (
+  OIDS=FALSE
+);
+ALTER TABLE public.prefix_nodes
+  OWNER TO postgres;
+
+-- Index: public.prefix_nodes_parent_id_index
+
+-- DROP INDEX public.prefix_nodes_parent_id_index;
+
+CREATE INDEX prefix_nodes_parent_id_index
+  ON public.prefix_nodes
+  USING btree
+  (parent_id);
+
+
+-- Trigger: after_change_node on public.prefix_nodes
+
+-- DROP TRIGGER after_change_node ON public.prefix_nodes;
+
+CREATE TRIGGER after_change_node
+  AFTER INSERT OR UPDATE
+  ON public.prefix_nodes
+  FOR EACH ROW
+  EXECUTE PROCEDURE public.after_change_node();
+
+------------------------------------------------------------------------------------------------------
+
+-- Table: public.prefix_nodes_paths
+
+-- DROP TABLE public.prefix_nodes_paths;
+
+CREATE TABLE public.prefix_nodes_paths
+(
+  ancestor_id uuid NOT NULL,
+  descendant_id uuid NOT NULL,
+  depth integer,
+  CONSTRAINT prefix_nodes_paths_pkey PRIMARY KEY (ancestor_id, descendant_id),
+  CONSTRAINT prefix_nodes_paths_ancestor_id_fkey FOREIGN KEY (ancestor_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE,
+  CONSTRAINT prefix_nodes_paths_descendant_id_fkey FOREIGN KEY (descendant_id)
+      REFERENCES public.prefix_nodes (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE CASCADE
+)
+WITH (
+  OIDS=FALSE
+);
+ALTER TABLE public.prefix_nodes_paths
+  OWNER TO postgres;
+
+-- Index: public.prefix_nodes_paths_ancestor_id_index
+
+-- DROP INDEX public.prefix_nodes_paths_ancestor_id_index;
+
+CREATE INDEX prefix_nodes_paths_ancestor_id_index
+  ON public.prefix_nodes_paths
+  USING btree
+  (ancestor_id);
+
+-- Index: public.prefix_nodes_paths_descendant_id_index
+
+-- DROP INDEX public.prefix_nodes_paths_descendant_id_index;
+
+CREATE INDEX prefix_nodes_paths_descendant_id_index
+  ON public.prefix_nodes_paths
+  USING btree
+  (descendant_id);
+
+
+------------------------------------------------------------------------------------------------------
+
+
+-- Function: public.get_child_nodes(uuid)
+
+-- DROP FUNCTION public.get_child_nodes(uuid);
+
+CREATE OR REPLACE FUNCTION public.get_child_nodes(uuid)
+  RETURNS SETOF record AS
+$BODY$
+SELECT d.id,
+       d.is_deleted,
+       d.parent_id,
+       concat(repeat('-', p.depth), d.name) AS name,
+       p.depth,
+       array_to_string(array_agg(crumbs.ancestor_id::CHARACTER VARYING),',','*') 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 = $1 AND d.is_deleted = false
+ GROUP BY d.id, p.depth
+ ORDER BY d.id ASC
+ ;
+$BODY$
+  LANGUAGE sql VOLATILE
+  COST 100
+  ROWS 1000;
+ALTER FUNCTION public.get_child_nodes(uuid)
+  OWNER TO postgres;
+
+
+------------------------------------------------------------------------------------------------------
+
+
+-- Function: public.get_children_by_uuid(uuid)
+
+-- DROP FUNCTION public.get_children_by_uuid(uuid);
+
+CREATE OR REPLACE FUNCTION public.get_children_by_uuid(uuid)
+  RETURNS json AS
+$BODY$
+DECLARE
+  result json;
+BEGIN
+SELECT array_to_json(array_agg(row_to_json(t))) INTO result -- inject output into result variable
+FROM ( -- same CTE as above
+  WITH RECURSIVE genres_materialized_path AS (
+    SELECT id, name, ARRAY[]::UUID[] AS path
+    FROM prefix_nodes WHERE parent_id IS NULL
+
+    UNION ALL
+
+    SELECT prefix_nodes.id, prefix_nodes.name, genres_materialized_path.path || prefix_nodes.parent_id::UUID
+    FROM prefix_nodes, genres_materialized_path
+    WHERE prefix_nodes.parent_id = genres_materialized_path.id
+  ) SELECT id, name, ARRAY[]::UUID[] AS children
+  FROM genres_materialized_path WHERE $1 = genres_materialized_path.path[array_upper(genres_materialized_path.path,1)] -- some column polish for a cleaner JSON
+) t;
+RETURN result;
+END;
+$BODY$
+  LANGUAGE plpgsql VOLATILE
+  COST 100;
+ALTER FUNCTION public.get_children_by_uuid(uuid)
+  OWNER TO postgres;
+
+
+
+------------------------------------------------------------------------------------------------------
+
+
+
+-- Function: public.get_tree(json)
+
+-- DROP FUNCTION public.get_tree(json);
+
+CREATE OR REPLACE FUNCTION public.get_tree(data json)
+  RETURNS json AS
+$BODY$
+var root = [];
+for(var i in data) {
+  build_tree(data[i]['id'], data[i]['name'], data[i]['children']);
+}
+function build_tree(id, name, children) {
+  var exists = getObject(root, id);
+  if(exists) {
+       exists['children'] = children;
+  }
+  else {
+    root.push({'id': id, 'name': name, 'children': children});
+  }
+}
+function getObject(theObject, id) {
+  var result = null;
+  if(theObject instanceof Array) {
+    for(var i = 0; i < theObject.length; i++) {
+      result = getObject(theObject[i], id);
+      if (result) {
+        break;
+      }
+    }
+  }
+  else
+  {
+    for(var prop in theObject) {
+      if(prop == 'id') {
+        if(theObject[prop] === id) {
+            return theObject;
+        }
+      }
+      if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
+        result = getObject(theObject[prop], id);
+        if (result) {
+            break;
+        }
+      }
+    }
+  }
+  return result;
+}
+return JSON.stringify(root);
+$BODY$
+  LANGUAGE plv8 IMMUTABLE STRICT
+  COST 100;
+ALTER FUNCTION public.get_tree(json)
+  OWNER TO postgres;
+
+
+------------------------------------------------------------------------------------------------------
+
+
+
+-- Function: public.move(uuid, uuid)
+
+-- DROP FUNCTION public.move(uuid, uuid);
+
+CREATE OR REPLACE FUNCTION public.move(
+    uuid,
+    uuid)
+  RETURNS void AS
+$BODY$
+UPDATE prefix_nodes SET parent_id = $2 WHERE id = $1;
+
+DELETE FROM prefix_nodes_paths
+WHERE descendant_id IN (SELECT descendant_id FROM prefix_nodes_paths WHERE ancestor_id = $1)
+AND ancestor_id     IN (SELECT ancestor_id   FROM prefix_nodes_paths WHERE descendant_id = $1 AND ancestor_id != descendant_id);
+
+INSERT INTO prefix_nodes_paths (ancestor_id, descendant_id, depth)
+SELECT supertree.ancestor_id, subtree.descendant_id, supertree.depth + subtree.depth + 1
+FROM prefix_nodes_paths AS supertree
+CROSS JOIN prefix_nodes_paths AS subtree
+WHERE supertree.descendant_id = $2
+AND subtree.ancestor_id = $1;
+$BODY$
+  LANGUAGE sql VOLATILE
+  COST 100;
+ALTER FUNCTION public.move(uuid, uuid)
+  OWNER TO postgres;
diff --git a/sql/p_prefix_nodes_add_new_paths_after_insert.sql b/sql/p_prefix_nodes_add_new_paths_after_insert.sql
deleted file mode 100644
index 48fb882..0000000
--- a/sql/p_prefix_nodes_add_new_paths_after_insert.sql
+++ /dev/null
@@ -1,34 +0,0 @@
-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_delete_nodes.sql b/sql/p_prefix_nodes_delete_nodes.sql
deleted file mode 100644
index ce8350a..0000000
--- a/sql/p_prefix_nodes_delete_nodes.sql
+++ /dev/null
@@ -1,22 +0,0 @@
-DELIMITER $$
-
-USE `_category`$$
-
-DROP PROCEDURE IF EXISTS `p_prefix_nodes_delete_nodes_after_update`$$
-
-CREATE DEFINER = `root` @`localhost` PROCEDURE `p_prefix_nodes_delete_nodes_after_update` (
-    `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 ;
-
diff --git a/sql/p_prefix_nodes_delete_nodes_after_update.sql b/sql/p_prefix_nodes_delete_nodes_after_update.sql
deleted file mode 100644
index ce8350a..0000000
--- a/sql/p_prefix_nodes_delete_nodes_after_update.sql
+++ /dev/null
@@ -1,22 +0,0 @@
-DELIMITER $$
-
-USE `_category`$$
-
-DROP PROCEDURE IF EXISTS `p_prefix_nodes_delete_nodes_after_update`$$
-
-CREATE DEFINER = `root` @`localhost` PROCEDURE `p_prefix_nodes_delete_nodes_after_update` (
-    `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 ;
-
diff --git a/sql/p_prefix_nodes_get_subtree_by_node_id.sql b/sql/p_prefix_nodes_get_subtree_by_node_id.sql
deleted file mode 100644
index bca6494..0000000
--- a/sql/p_prefix_nodes_get_subtree_by_node_id.sql
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 517ebe3..0000000
--- a/sql/p_prefix_nodes_move_old_paths_after_update.sql
+++ /dev/null
@@ -1,46 +0,0 @@
-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
-    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 ;
diff --git a/sql/prefix_nodes_add_new_paths_after_insert.sql b/sql/prefix_nodes_add_new_paths_after_insert.sql
deleted file mode 100644
index 99cddf9..0000000
--- a/sql/prefix_nodes_add_new_paths_after_insert.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-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
deleted file mode 100644
index a1ae020..0000000
--- a/sql/prefix_nodes_move_old_paths_after_update.sql
+++ /dev/null
@@ -1,24 +0,0 @@
-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
-    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_prefix_nodes_move_old_paths_after_update(NEW.`id`, NEW.`parent_id`);
-    END IF;
-
-    IF OLD.`is_deleted` != NEW.`is_deleted` THEN
-        CALL p_prefix_nodes_delete_nodes_after_update(NEW.`parent_id`, NEW.`is_deleted`);
-    END IF;
-END;
-$$
-
-DELIMITER ;