Tree structure, don’t use recursion to implement it, this is the best solution; faster, stronger and more practical


Hello everyone, I am Yihang;

Whether you are doing front-end or back-end development, I believe that you must have encountered the need for a tree structure. Especially for management platform-type projects, there will generally be a tree-structured menu bar. Another example is the company organizational structure . , requirements such as hierarchical relationships , belonging relationships , etc. are essentially a manifestation of the tree structure;

When encountering this kind of demand, the most common and easiest design idea to think of is: the parent-child relationship , the child saves his parent ID through a field, and then obtains the hierarchical relationship recursively;

A few days ago, some friends in the technical exchange group were asking: In actual business, there is too much data in the tree structure and the levels are deep. After the query process is recursive, the performance is relatively poor. Is there anything better? Design ideas make queries and statistics more convenient and efficient;

Today I will introduce to you a new tree structure design plan: the improved pre-order tree method has far greater advantages in query and statistics than the recursive design plan of the parent-child relationship;

This article will explain in detail and compare the implementation methods, advantages and disadvantages of the two solutions.

Article directory:

Regarding the demand for tree structures, no matter what method is used, the problems to be solved are the same. Here are the common problems of tree structures:

  • Addition, deletion and modification of nodes
  • Whether there are child nodes (leaf nodes)
  • Query all child nodes
  • Query all grandchild nodes
  • Query all descendant nodes
  • Parent node query
  • Ancestor node query
  • Count the number of all descendant departments

In response to the above problems, let’s take a simple example of a company’s organizational structure to see how the two solutions are implemented and solved.

All the examples in this article are implemented using MySQL+Java. The core ideas are similar. In actual use, you can adjust them according to the language characteristics and your own habits.

Father-son relationship plan

The parent-child relationship, as the name suggests, means that the current node only pays attention to who its parent node is and saves it. To query which child nodes I have, I only need to globally find all items whose parent ID is consistent with my ID;

As shown below:

Program features

  • advantage

    • The solution is simple and easy to understand

    • Data structure is simple and clear

    • The hierarchy is intuitive and clear

    • Easy to maintain

      Hierarchical relationships only need to pay attention to their own parent ID, so when adding or modifying, once the relationship changes, just adjust the corresponding parent ID.

  • shortcoming

    • Search trouble, statistics trouble

      According to the data of the current node, only the data of the child nodes can be obtained. Once the query and statistics exceed the scope of the parent and child, it can only be searched layer by layer through recursion;

Example

According to the above graphic example, the corresponding table structure is as follows:

ID dep_name (department name) level parent_id (father ID)
1 Board of Directors 1 0
2 General manager 2 1
3 Secretary of the Board of Directors 2 1
4 Product Department 3 2
5 Administrative Director 3 2
6 Department of Design 4 4
7 Technology Department 4 4
8 Finance Department 4 5
9 Administration Department 4 5
10 client 5 7
11 Server 5 7

SQL script:

DROP TABLE IF EXISTS `department_info`;
CREATE TABLE `department_info`  (
  `id` int(11) NOT NULL,
  `dep_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `level` int(11) NULL DEFAULT NULL,
  `parent_id` int(11) NULL DEFAULT NULL COMMENT '父ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `department_info` VALUES (1, '董事会', 1, 0);
INSERT INTO `department_info` VALUES (2, '总经理', 2, 1);
INSERT INTO `department_info` VALUES (3, '董事会秘书', 2, 1);
INSERT INTO `department_info` VALUES (4, '产品部', 3, 2);
INSERT INTO `department_info` VALUES (5, '行政总监', 3, 2);
INSERT INTO `department_info` VALUES (6, '设计部', 4, 4);
INSERT INTO `department_info` VALUES (7, '技术部', 4, 4);
INSERT INTO `department_info` VALUES (8, '财务部', 4, 5);
INSERT INTO `department_info` VALUES (9, '行政部', 4, 5);
INSERT INTO `department_info` VALUES (10, '客户端', 5, 7);
INSERT INTO `department_info` VALUES (11, '服务端', 5, 7);

Creation of functions

Since the query of parent and child nodes requires recursion, for the convenience of query, two functions are created here:

  • Function to recursively query descendant node IDs

    DROP FUNCTION IF EXISTS queryChildrenDepInfo;
    DELIMITER ;;
    CREATE FUNCTION queryChildrenDepInfo(dep_id INT)
    RETURNS VARCHAR(4000)
    BEGIN
    	DECLARE sTemp VARCHAR(4000);
    	DECLARE sTempChd VARCHAR(4000);
    	SET sTemp='$';
    	SET sTempChd = CAST(dep_id AS CHAR);
    	WHILE sTempChd IS NOT NULL DO
    		SET sTemp= CONCAT(sTemp,',',sTempChd);
    		SELECT GROUP_CONCAT(id) INTO sTempChd FROM department_info WHERE FIND_IN_SET(parent_id,sTempChd)>0;
    	END WHILE;
    	RETURN sTemp;
    END
    ;;
    DELIMITER ;
    

    Test: Query all grandchild nodes under the technical department?

    SELECT queryChildrenDepInfo(4);
    

    SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(4));
    

  • Function to recursively query ancestor node IDs

    DROP FUNCTION IF EXISTS queryParentDepInfo;
    DELIMITER;;
    CREATE FUNCTION queryParentDepInfo(dep_id INT)
    RETURNS VARCHAR(4000)
    BEGIN
    	DECLARE sTemp VARCHAR(4000);
    	DECLARE sTempChd VARCHAR(4000);
    	SET sTemp='$';
    	SET sTempChd = CAST(dep_id AS CHAR);
    	SET sTemp = CONCAT(sTemp,',',sTempChd);
    	SELECT parent_id INTO sTempChd FROM department_info WHERE id = sTempChd;
    	WHILE sTempChd <> 0 DO
    		SET sTemp = CONCAT(sTemp,',',sTempChd);
    		SELECT parent_id INTO sTempChd FROM department_info WHERE id = sTempChd;
    	END WHILE;
    	RETURN sTemp;
    END
    ;;
    DELIMITER ;
    

    Test: Query all ancestor nodes of the technical department?

    SELECT queryParentDepInfo(7);
    

    SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(7));
    

Addition, deletion and modification of nodes

  • Add node

    For example, if you want to add a testing department under the technical department

    INSERT INTO department_info(`id`, `dep_name`, `level`, `parent_id`) VALUES (12, '测试部', 5, 7);
    
  • Modify node

    For example: Put the test department (ID = 12) under the product department (ID = 4); you only need to change the parent node ID corresponding to the test department to 4.

    SET @id = 12;
    SET @pid = 4;
    UPDATE department_info SET `parent_id` = @pid WHERE `id` = @id;
    
  • Delete node

    Compared with adding and modifying, deletion is a bit special. If the deleted node has child nodes, it means that the child nodes also need to be deleted simultaneously;

    Therefore, we need to use the function created above to recursively query the descendant node ID (queryChildrenDepInfo)

    For example: delete the technical department;

    DELETE FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(7));
    

Whether there are child nodes (leaf nodes)

Under this scheme, there are two ways to determine whether it is a leaf node:

  • Count the number of current nodes and descendant nodes

    Recursively query the IDs of all child nodes and count the number. Since the function query includes the node itself, it is used here COUNT(*)-1to calculate the number of child nodes. If it is equal to 0, it is a leaf node. If it is greater than 0, it means it is not a leaf node;

    -- 查看设计部(ID=6)是不是叶子节点
    SET @id = 6;
    -- 由于数量包含了自身,由于统计的是子节点的数量,所以这里需要-1将自己去掉
    SELECT COUNT(*)-1 FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));
    

  • Add tags for leaf nodes

    Add an isLeaf field to the table. When a node is added, deleted or modified, use this field to mark whether it is a leaf node.

Query all subordinate departments (sub-nodes)

To query all subordinate departments, you need to use the id and level fields of the current node.

Example: Query the child nodes of the product department (id = 4, level = 3)

SET @id = 4;
SET @lv = 3;
SELECT * from department_info where parent_id = @id AND `level` = @lv + 1;

Query all subordinate departments (grandchild nodes)

In actual business scenarios, this requirement is rare. It is mainly used to demonstrate the operability and does not rule out its use in special occasions.

Querying the grandchild node is much more troublesome than the child node, because there is no data relationship between the current node and the grandchild node, so you need to use the child node to find the grandchild node, so you must use recursive query of the descendant node ID here. function;

Example: Query the grandchild node of the product department (id = 4, level = 3)

-- 查询孙节点
SET @id = 4;
SET @lv = 3;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id)) AND `level` = @lv + 2;

Search all subordinate departments

Querying subordinate departments is similar to querying subordinate departments (grandchild nodes), except that there is no need to check the hierarchy;

Example: Query all subordinate departments under the product department?

SET @id = 4;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));

Check the department you belong to

That is to query the parent node; in this solution, the ID of the parent node has been saved in the node, and the parent node can be obtained directly through the ID.

Query all superior departments

Since the current node only saves the parent node ID, higher-level information can only be obtained level by level through recursion;

Example: Query all superior departments of the technical department (id = 7);

SET @id = 7;
SELECT * FROM department_info WHERE FIND_IN_SET(id,queryParentDepInfo(@id));

Count the number of all subordinate departments

It is the same as querying whether it is a leaf node, but the way of interpreting the obtained data is different;

Example: How many subordinate departments does the Statistics and Technology Department have?

SET @id = 7;
SELECT COUNT(*)-1 FROM department_info WHERE FIND_IN_SET(id,queryChildrenDepInfo(@id));

Improved preorder tree scheme

It can be seen from the above parent-child relationship scheme that most operations require recursive querying of all descendant nodes. If there are many levels and depths mentioned at the beginning of the article, the recursive process will greatly affect the query. , statistical performance;

Let's introduce the improved tree structure scheme of the pre-order tree. The node no longer saves the ID of the parent node, but adds lvalue and rvalue to each node :

As shown below:

the corresponding table data is as follows:

id dep_name (department name) lt(lvalue) rt(rvalue) lv(level)
1 Board of Directors 1 22 1
2 General manager 2 19 2
3 Secretary of the Board of Directors 20 21 2
4 Product Department 3 12 3
5 Administrative Director 13 18 3
6 Department of Design 4 5 4
7 Technology Department 6 11 4
8 Finance Department 14 15 4
9 Administration Department 16 17 4
10 client 7 8 5
11 Server 9 10 5

SQL statement:

DROP TABLE IF EXISTS `department_info2`;
CREATE TABLE `department_info2`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dep_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',
  `lt` int(11) NOT NULL COMMENT '节点左数',
  `rt` int(11) NOT NULL COMMENT '节点右数',
  `lv` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `department_info2` VALUES (1, '董事会', 1, 22, 1);
INSERT INTO `department_info2` VALUES (2, '总经理', 2, 19, 2);
INSERT INTO `department_info2` VALUES (3, '董事会秘书', 20, 21, 2);
INSERT INTO `department_info2` VALUES (4, '产品部', 3, 12, 3);
INSERT INTO `department_info2` VALUES (5, '行政总监', 13, 18, 3);
INSERT INTO `department_info2` VALUES (6, '设计部', 4, 5, 4);
INSERT INTO `department_info2` VALUES (7, '技术部', 6, 11, 4);
INSERT INTO `department_info2` VALUES (8, '财务部', 14, 15, 4);
INSERT INTO `department_info2` VALUES (9, '行政部', 16, 17, 4);
INSERT INTO `department_info2` VALUES (10, '客户端', 7, 8, 5);
INSERT INTO `department_info2` VALUES (11, '服务端', 9, 10, 5);

Program features

  • advantage

    • Query summary is simple and efficient
    • No need for recursive queries, high performance
  • shortcoming

    • The structure is relatively complex and the data level is difficult to understand.

    • Not easy to maintain

      It is believed that the existence of left and right values ​​will directly affect subsequent nodes. Therefore, when the current node is added, deleted or modified, it will affect the subsequent node industry;

Example

Addition, deletion and modification of nodes

  • New

    As shown in the figure below: Add a test department under the technical department, and the left and right values ​​​​corresponding to the new nodes are 11 and 12 respectively;


    Process analysis:

    In the first step , all left numbers greater than 11 (the left number of the new node) are +2 (purple part)

    In the second step , all right numbers greater than 12 (the right number of the new node) are +2 (orange part)

    The third step is to add department nodes with left and right numbers 11 and 12 respectively.

    Since there are multiple steps involved here, we need to ensure the atomicity of database operations, so transaction operations are required. For convenience, a stored procedure is created here; the stored procedure is not necessary, and transactions can also be guaranteed in the form of code. Operation; just use stored procedures, the reliability will be higher;

    Add node stored procedure :

    DROP PROCEDURE IF EXISTS insertDepartment;
    CREATE PROCEDURE insertDepartment(IN lt_i INT,IN rt_i INT,IN lv_i INT,IN dn VARCHAR(256))
    BEGIN
    	  DECLARE d_error INTEGER DEFAULT 0;
        DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- 异常时设置为1
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- 如果表中没有下一条数据则置为2
    		
    		START TRANSACTION;
    		-- 将所有左值大于新增左值的部门全部+2
    		UPDATE department_info2 SET lt=lt+2 WHERE lt > lt_i;
    		-- 将所有右值大于新增右值的部门全部+2
    		UPDATE department_info2 SET rt=rt+2 WHERE rt >= lt_i;
    		-- 新增部门
    		INSERT INTO department_info2(dep_name,lt,rt,lv) VALUES(dn,lt_i,rt_i,lv_i);
    		SET d_error= ROW_COUNT();
    
        IF d_error != 1 THEN
            ROLLBACK;-- 结果不等于1 的时候,说明新增失败,回滚操作
        ELSE
            COMMIT; -- 等于1的时候,说明添加成功,提交
        END IF;
        select d_error;
    END
    

    Input parameters

    lt_i : lvalue of new department

    rt_i : rvalue of new department

    lv_i : Level of administrative department

    dn : department name

    As shown in the example above, you can call the new stored procedure to add:

    call insertDepartment(11, 12, 5,"测试部");
    

  • Revise

    Ordinary node information modification, I won’t go into details here, it is very simple;

    Node movement is the most complex modification operation under this solution , because the entire process involves adjustments in multiple dimensions such as location changes and hierarchical changes, and transaction operations must also be guaranteed;

    Example: We want to put the technical department (id = 4) under the direct responsibility of the general manager (id = 2), that is, move it under the general manager;

    Process analysis:

    The first step is to calculate the difference between the left and right numbers of the technical department

    The second step is to calculate the difference with the moved superior node.

    Step 3 : Determine whether to move left or right

    Step 4 : Subtract (shift left)/add (shift right) the difference between this node and the target node

    The fifth step is to subtract (shift left)/add (shift right) the difference between the moved node and the descendant node and the parent node.

    Step 6 : Adjust levels

    The whole process is shown in the figure below. It is a little complicated. You can understand it carefully by combining the diagram and the code of the stored procedure:

    In order to facilitate operation and avoid errors, the core logic is also implemented in the form of stored procedures to reduce the risk of errors:

    DROP PROCEDURE IF EXISTS moveDepartment;
    CREATE PROCEDURE moveDepartment(IN fid INT,IN tid INT)
    BEGIN
    	  DECLARE d_error INTEGER DEFAULT 0;
    		DECLARE num INTEGER DEFAULT 0; -- 删除节点左右值之间的差值
    		DECLARE mnum INTEGER DEFAULT 0; -- 移动阶段与上级节点之间的差值
    		DECLARE ids VARCHAR(1000); -- 保存所有正在移动的id集合,保证多个节点移动时也能正常
    		DECLARE blt INT; -- 需要移动节点的左数
    		DECLARE brt INT; -- 需要移动节点的右数
    		DECLARE blv INT; -- 需要移动节点的层级
    		DECLARE tlt INT; -- 目标节点的左数
    		DECLARE trt INT; -- 目标节点的右数
    		DECLARE tlv INT; -- 目标节点的层级
        DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- 异常时设置为1
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- 如果表中没有下一条数据则置为2
    		
    		START TRANSACTION;
    		-- 查询待移动的节点的左右数以及层级
    		SELECT lt,rt,lv	INTO blt, brt,blv FROM department_info2 WHERE id = fid;
    		-- 查询目标节点的左右数以及层级
    		SELECT lt,rt,lv INTO tlt, trt,tlv FROM department_info2 WHERE id = tid;
    		-- 查询所有要一定的节点,当前节点以及其子孙节点
    		SELECT GROUP_CONCAT(id) INTO ids FROM department_info2 WHERE lt>=blt AND rt <=brt;
    		IF tlt > blt AND trt < brt THEN
    		  -- 将自己移动到自己的下属节点 暂时不允许 如果要如此操作,需要将下级调整为平级 再移动
    			SET d_error = -4;
    		ELSEIF blt > tlt AND brt < trt AND blv = tlv + 1 THEN
    		  -- 说明本身就已经是目标节点在子节点了,不需要处理,直接成功
    		  SET d_error = 0;
    		ELSE
    		  -- 计算移动节点与上级节点之间的差值
    			SET mnum = trt - brt -1;
    			-- 计算当前节点及其子节点的差值
    			SET num = brt - blt + 1;
    			
    			-- 首先将移动前的节点整个元素表中移除掉
    			IF trt > brt THEN
    			  -- 左往右移动
    				UPDATE department_info2 SET lt=lt-num WHERE lt > brt AND lt < trt;
    				UPDATE department_info2 SET rt=rt-num WHERE rt > brt AND rt < trt;
    			ELSE
    			  -- 从右往左移动 将系数全部变为负值,-负数就等于+正数
    				SET mnum = trt - blt;
    				SET num = -num;
    				UPDATE department_info2 SET lt=lt-num WHERE lt >= trt AND lt < blt;
    				UPDATE department_info2 SET rt=rt-num WHERE rt >= trt AND rt < blt;
    			END IF;
    			
    			-- 调整移动的节点以及下属节点
    			UPDATE department_info2 SET lt=lt+mnum,rt=rt+mnum,lv = lv - (blv - tlv -1) WHERE FIND_IN_SET(id,ids);
    			SET d_error= ROW_COUNT();
    			
    			IF d_error <= 0 THEN
    					ROLLBACK;
    			ELSE
    					COMMIT;
    			END IF;
    		END IF;
        select d_error;
    END
    

    Input parameters

    fid : moved node id

    tid : target node id

    test:

    CALL moveDepartment(7,2)
    

  • delete

    The deletion process is just the opposite of adding a new one. When deleting a node and its own point, all left and right values ​​greater than the deleted node need to be subtracted from the left and right difference +1 of the deleted node;

    As shown in the figure below: the process of deleting the technical department

    :

    The first step is to calculate the left and right difference +1 of the deleted node; the left and right values ​​of the technical department are 6 and 11 respectively, and the difference +1: 11 - 6 + 1

    The second step is to delete all child nodes of the node machine.

    The third step is to subtract the difference from all nodes that are greater than the left and right values ​​of the deleted node.

    Also for convenience, we also create a stored procedure:

    DROP PROCEDURE IF EXISTS removeDepartment;
    CREATE PROCEDURE removeDepartment(IN did INT)
    BEGIN
    	  DECLARE d_error INTEGER DEFAULT 0;
    		DECLARE num INTEGER DEFAULT 0; -- 删除节点左右值之间的差值
    		DECLARE dlt INT; -- 删除节点的左值
    		DECLARE drt INT; -- 删除节点的右值值
        DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET d_error= -2;-- 异常时设置为1
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET d_error = -3; -- 如果表中没有下一条数据则置为2
    		
    		START TRANSACTION;
    		-- 查询删除节点的左右值
    		SELECT lt,rt INTO dlt, drt FROM department_info2 WHERE id = did;
    		-- 计算当前节点及其子节点的差值
    		SET num = drt - dlt + 1;
    		
    		-- 删除当前节点及其子节点
    		DELETE FROM department_info2 WHERE lt>=dlt AND rt<=drt;
    		SET d_error= ROW_COUNT();
    		
    		-- 左右值减少对应的插值
    		UPDATE department_info2 SET lt=lt-num WHERE lt > dlt;
        UPDATE department_info2 SET rt=rt-num WHERE rt > drt;
    		
        IF d_error != num/2 THEN -- 判断删除的数量与当前节点+子孙节点的数量是否一致;不一致的话,直接回滚
            ROLLBACK;
        ELSE
            COMMIT;
        END IF;
        select d_error;
    END
    

    test:

    -- 设计部的id = 7
    SET @id=7;
    call removeDepartment(@id);
    

Whether there are child nodes (leaf nodes)

Without additional query, you can directly judge whether it is a leaf node by the left and right numbers of the node; when the right number - the left number = 1 , it means that the current node is a leaf node, otherwise it is not;

Query all subordinate departments

Equivalent to: query all nodes whose left number is greater than the current node, whose right number is smaller than the current node, and whose level is 1 greater than the current node;

Example: Query all subordinate departments of the product department (lt = 3, rt = 12, lv = 3):

SET @lt = 3;  -- 节点左数
SET @rt = 12; -- 节点右数
SET @lv = 3;  -- 当先的层级
SELECT * from department_info2 where lt > @lt AND rt < @rt AND `lv` = @lv + 1

Query all subordinate departments (grandchild nodes)

This requirement rarely occurs in actual business, and is only used for feasibility demonstration;

The query of grandchild nodes is similar to that of child nodes, except that the level changes from +1 to +2

Example: Query all subordinate departments of the product department (lt = 3, rt = 12, lv = 3):

SET @lt = 3;  -- 节点左数
SET @rt = 12; -- 节点右数
SET @lv = 3;  -- 当先的层级
SELECT * from department_info2 where lt > @lt AND rt < @rt AND `lv` = @lv + 2;

Search all subordinate departments

Equivalent to: nodes larger than the current node on the left and smaller on the right, all descendant nodes;

For example, query all subordinate departments of the product department (lt = 3, rt = 12):

SET @lt = 3;  -- 节点左数
SET @rt = 12; -- 节点右数
SELECT * from department_info2 where lt > @lt AND rt < @rt;

Check the department you belong to

Equivalent to: the node with the left number smaller than itself, the right number larger than itself, and level -1, which is the parent node.

For example, query the subordinate departments of the Technology Department (lt = 6, rt = 11):

SET @lt = 6;  -- 节点左数
SET @rt = 11; -- 节点右数
SET @lv = 4;  -- 当先的层级
SELECT * from department_info2 where lt < @lt AND rt > @rt AND `lv` = @lv - 1 ;

Check all superior departments

It is similar to querying the parent node, except that there is no need to check the level. All left numbers are smaller than itself and all right numbers are bigger than itself. They are their ancestor nodes;

For example, query all superior departments of the product department (lt = 3, rt = 12)

SET @lt = 3;  -- 节点左数
SET @rt = 12; -- 节点右数
SELECT * from department_info2 where lt < @lt AND rt > @rt;

Count the number of all subordinate departments

To count the number of descendant nodes, no additional query is needed. You can calculate the number of child nodes based on your own left and right numbers;

Calculation formula: (right number - left number - 1) / 2 ;

For example, calculate the number of subordinate departments of the product department (id = 4):

SELECT dep_name,(rt - lt - 1) / 2 as num FROM department_info2 where id = 4

Format data

Regardless of the solution, the data level is flat, and structured relationships are expressed only through field logic. After querying, how to structure the data into a tree structure and display it? The following introduces the two recursive and non-recursive methods. Ways to implement:

  • basic objects

    @Data
    @RequiredArgsConstructor
    public class LrItem {
          
          
    
        @NonNull
        private Integer id;
    
        @NonNull
        private String depName;
    
        @NonNull
        private Integer left;
    
        @NonNull
        private Integer right;
    
        private Integer level;
    
        private Integer parentId;
    
        /**
         * 是否是叶子
         */
        private Boolean isLeaf;
    
        private List<LrItem> childItem;
    }
    
  • Test Data

    This is just a demonstration of formatted data, and it is not that complicated; in actual business scenarios, this batch of data is generally queried from the database.

    List<LrItem> deps = new ArrayList<>();
    deps.add(new LrItem(1, "董事会", 1, 22));
    deps.add(new LrItem(2, "总经理", 2, 19));
    deps.add(new LrItem(3, "董事会秘书", 20, 21));
    deps.add(new LrItem(4, "产品部", 3, 12));
    deps.add(new LrItem(5, "行政总监", 13, 18));
    deps.add(new LrItem(6, "设计部", 4, 5));
    deps.add(new LrItem(7, "技术部", 6, 11));
    deps.add(new LrItem(8, "财务部", 14, 15));
    deps.add(new LrItem(9, "行政部", 16, 17));
    deps.add(new LrItem(10, "客户端", 7, 8));
    deps.add(new LrItem(11, "服务端", 9, 10));
    
  • Organize data

    public static void init(List<LrItem> deps) {
          
          
        // 如果数据库排序过了之后  这里就不用排序了
        deps.sort(Comparator.comparing(LrItem::getLeft));
    
        // 为计算层级 缓存节点右侧的值
        List<Integer> rights = new ArrayList<>();
        Map<Integer, Integer> mp = new HashMap<>();
    
        // 初始化节点的层级,叶子节点 以及 父节点ID 对应的数据
        deps.forEach(item -> {
          
          
            if (rights.size() > 0) {
          
          
                // 一旦发现本次节点右侧的值比前一次的大,说明出现层级上移了 需要移除前一个底层及的值
                // 这里大部分情况下只存在移除前面一个值情况
                while (rights.get(rights.size() - 1) < item.getRight()) {
          
          
                    rights.remove(rights.size() - 1);//从rights末尾去除
                }
            }
            Integer _level = rights.size() + 1;
            item.setLevel(_level);
            mp.put(_level, item.getId());
            item.setParentId(mp.containsKey(_level - 1) ? mp.get(_level - 1) : 0); //计算出上级部门编号
            item.setIsLeaf(item.getLeft() == item.getRight() - 1);//判断是否叶子部门
            rights.add(item.getRight());
        });
    
        System.out.println(rights);
        System.out.println(mp);
    }
    

Recursive sorting

The idea of ​​recursion is relatively simple and clear, that is, after getting the current node, find its own child nodes among all nodes. When all nodes have been searched, the entire tree structure process is complete;

We can combine the new features of Java 8 Stream to make the entire recursive code relatively simple and clear;

/**
 * @param deps 所有数据
 */
public static void recursive(List<LrItem> deps) {
    
    
    init(deps);
    //获取父节点
    List<LrItem> collect = deps.stream()
            .filter(m -> m.getParentId() == 0)
            .map(m ->
                    {
    
    
                        m.setChildItem(getChildrens(m, deps));
                        return m;
                    }
            ).collect(Collectors.toList());

    // 普遍请求下,根节点只会有一个,所以这里取出第一个元素,如果由多个,可根据需求调整,这里仅做测试使用
    System.out.println(JSON.toJSON(collect.get(0)));
}

/**
 * 递归查询子节点
 *
 * @param root 根节点
 * @param all  所有节点
 * @return 根节点信息
 */
private static List<LrItem> getChildrens(LrItem root, List<LrItem> all) {
    
    
    List<LrItem> children = all.stream()
            .filter(m -> Objects.equals(m.getParentId(), root.getId()))
            .map(m -> {
    
    
                        m.setChildItem(getChildrens(m, all));
                        return m;
                    }
            ).collect(Collectors.toList());
    return children;
}

Non-recursive sorting in reverse order

This is a plan that trades space for time ;

The characteristics of this method: After the data is sorted hierarchically, only one for loop is needed to sort out the structured data.

  • The first step is to calculate the level and parent node ID

  • The second step is to sort by level

  • The third step is to traverse the root node from the deepest node in reverse order.

    The traversal process Map<Integer, List<LrItem>>caches the data of the ID and the current node in the way childItemof

public static void reverseFormat(List<LrItem> deps) {
    
    
    init(deps);

    deps.sort(Comparator.comparing(LrItem::getLevel));
    deps.forEach(item -> System.out.println(JSON.toJSONString(item)));

    // 临时缓存各自节点的容器
    Map<Integer, List<LrItem>> childCache = new HashMap<>();

    // 当前节点
    LrItem lrItem = null;
    //int level = 0;
    // 采用倒序遍历,整理各个子节点的集合
    for (int i = deps.size() - 1; i >= 0; i--) {
    
    
        lrItem = deps.get(i);
        Integer parentId = lrItem.getParentId();
        if (null == lrItem || null == parentId) {
    
    
            continue;
        }

        List<LrItem> childItems = childCache.get(parentId);
        if (null == childItems) {
    
    
            childCache.put(parentId, childItems = new ArrayList<>());
        }
        childItems.add(lrItem);

        // 如果不是叶子节点的时候,说明他肯定有子节点,去缓存中找到,回填回去
        if (!lrItem.getIsLeaf()) {
    
    
            childItems = childCache.get(lrItem.getId());
            childItems.sort(Comparator.comparing(LrItem::getId));
            lrItem.setChildItem(childItems);
            childCache.remove(lrItem.getId());
        }
    }

    System.out.println(JSON.toJSONString(lrItem));
}

Formatted data

Either way, you will end up with the following structured data;

{
    
    
    "depName": "董事会",
    "id": 1,
    "isLeaf": false,
    "left": 1,
    "level": 1,
    "prientId": 0,
    "right": 22,
    "childItem": [
        {
    
    
            "depName": "总经理",
            "id": 2,
            "isLeaf": false,
            "left": 2,
            "level": 2,
            "prientId": 1,
            "right": 19,
            "childItem": [
                {
    
    
                    "depName": "行政总监",
                    "id": 5,
                    "isLeaf": false,
                    "left": 13,
                    "level": 3,
                    "prientId": 2,
                    "right": 18,
                    "childItem": [
                        {
    
    
                            "depName": "设计部",
                            "id": 6,
                            "isLeaf": true,
                            "left": 4,
                            "level": 4,
                            "prientId": 4,
                            "right": 5
                        },
                        {
    
    
                            "depName": "技术部",
                            "id": 7,
                            "isLeaf": false,
                            "left": 6,
                            "level": 4,
                            "prientId": 4,
                            "right": 11,
                            "childItem": [
                                {
    
    
                                    "depName": "客户端",
                                    "id": 10,
                                    "isLeaf": true,
                                    "left": 7,
                                    "level": 5,
                                    "prientId": 7,
                                    "right": 8
                                },
                                {
    
    
                                    "depName": "服务端",
                                    "id": 11,
                                    "isLeaf": true,
                                    "left": 9,
                                    "level": 5,
                                    "prientId": 7,
                                    "right": 10
                                }
                            ]
                        }
                    ],
                    "depName": "产品部",
                    "id": 4,
                    "isLeaf": false,
                    "left": 3,
                    "level": 3,
                    "prientId": 2,
                    "right": 12
                },
                {
    
    
                    "childItem": [
                        {
    
    
                            "depName": "财务部",
                            "id": 8,
                            "isLeaf": true,
                            "left": 14,
                            "level": 4,
                            "prientId": 5,
                            "right": 15
                        },
                        {
    
    
                            "depName": "行政部",
                            "id": 9,
                            "isLeaf": true,
                            "left": 16,
                            "level": 4,
                            "prientId": 5,
                            "right": 17
                        }
                    ]
                }
            ]
        },
        {
    
    
            "depName": "董事会秘书",
            "id": 3,
            "isLeaf": true,
            "left": 20,
            "level": 2,
            "prientId": 1,
            "right": 21
        }
    ]
}

Compare

In view of the above detailed explanation, let’s compare the two more intuitively in the form of a table:

Function Father-son relationship plan preordinal scheme
New Simple complex
Revise Simple complex
delete complex complex
Determine leaf nodes Complex (unless it increases the difficulty of editing and organizes it in advance) Simple
Query child nodes Simple Simple
Query grandchild node complex Simple
Query parent node Simple Simple
Query ancestor nodes complex Simple
Count the number of descendant nodes complex Simple
Applicable scene Simple structure, few levels, few statistics, frequent changes The structure is complex, with few changes and deep levels, requiring complex statistics.

Summarize

After analyzing the common scenarios of the two schemes, I found that each has its own advantages and disadvantages, so forgive me for the slightly over-the-top title. In comparison, I personally prefer the improved ordinal scheme.

The parent-child relationship solution is suitable for needs with a relatively simple structure and few levels;

The ordinal-first scheme is more suitable for needs with complex structures, few changes, deep levels, and frequent summary statistics;

So, again, there is no absolute good plan, only suitable scenarios ; you need to be more aware of the actual situation of your business and choose the plan that is most beneficial to the project as appropriate.

Okay, that’s it for today’s sharing; if you have any questions, please feel free to communicate and make corrections!

Thank you very much for your likes, follows and favorites!

Guess you like

Origin blog.csdn.net/lupengfei1009/article/details/124925230