数据库递归树形查询优化

我们经常在业务中会遇到无限上下级关系的表,比如组织机构表,一般来说这种表有两种设计方式,一是,表内两个字段自关联(属于物理上的关联),二是,表内根据一个字段的规律来判断(属于逻辑上的关联)

这个时候我们在查询等级关系时,第二种表设计的查询方式是: 使用 LIke '**__' 进行模糊查询,而第一种表设计方式就是 使用递归查询了,

ORACLE中可以使用 START WITH ....  CONNECT BY PRIOR ... 进行递归查询,而Mysql则只能使用存储过程来执行递归查询了,递归查询网上的教程就比较多,我这里就不一一概述了,这里讲递归查询的优化

首先 我的表设计如图所示

组织表【  organization 】:

组织关系表【 organization_concern 】:

 

 

 表关系一目了然,唯一有差别的是我这里多了一张关系表,当然也可以看出 organization_concern.R_ID = organization.ID   organization_concern.R_PID = organization.PID

这里所做的优化 就是通过一张关联表进行连接查询 从而避免递归查询,来进行优化的!

那么这个时候我们就需要为这张关系表提供数据了,通过存储过程,因为我们不能手动去填关系

CREATE DEFINER=`root`@`%` PROCEDURE `DATA_CONVERSION`()
BEGIN
        #所有变量应在游标前定义
        #子ID集合
        DECLARE STR_IDS VARCHAR(255) ;
        #分隔IDS集合的索引
        DECLARE V_INDEX CHAR(255);
        DECLARE V_ID CHAR(255);
        #外层游标赋值变量
        DECLARE S_ID INT(11);
        DECLARE NO_ROW_FOUND INT DEFAULT 0;
        
        #定义外层游标[定义游标,与打开游标之间尽量隔几行代码,NAVICAT可能会报错]
        DECLARE OUTER_CUR CURSOR FOR SELECT ID FROM organization;
        #错误[Err] 1329 - No data - zero rows fetched, selected, or processed   解决方式:http://blog.sina.com.cn/s/blog_544c72960101bvl3.html    
        #需要注意的是这个HANDLER变量需要声明到游标后面[如果不加该行  游标为空的时候  就会报错]
        #https://blog.csdn.net/liyongshun82/article/details/52813711  CONTINUE
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET NO_ROW_FOUND = 1;
        
        #当组织机构表发生变化时,就需要运行该存储过程,因为组织关系变化种类繁多所以需要清空关系表,再重新生成
        TRUNCATE TABLE organization_concern;

        #打开游标
        OPEN OUTER_CUR;
        OUTER_LOOP:Loop
            
            #这里设置为1是因为在进行字符串截取时我的字符串第一项为空字符串,所以直接略过第一项
            SET V_INDEX = 1;
            #设为空字符串准备下次循环
            SET STR_IDS ='';
            #SELECT '游标循环!!!';
            #游标赋值
            FETCH OUTER_CUR INTO S_ID;
            #如果游标的值为NULL,则会触发前面的异常处理HANDLER,且将NO_ROW_FOUND置为1,由于是按照CONTINUE来处理的所以代码继续执行
            #此处判断NO_ROW_FOUND是否为1,如果为1则跳出循环,由于本例中游标是通过查询得到的,所以如果游标为NULL,说明游标已遍历完结果集,但令人疑惑的是mysql的存储过程遍历完结果集并不自动退出循环
            IF NO_ROW_FOUND = 1 THEN
                #对于循环有两个操作:LEAVE表示离开循环,好比编程里面的break一样;ITERATE则继续循环,好比编程里面的continue一样。
                LEAVE  OUTER_LOOP;
            END IF;
            
            #主键变量
            SET V_ID=CAST(S_ID AS CHAR);
            
            #此WHILE的作用是将传入的ID,找到它所有的子节点的ID,并将其转换成字符串放到STR_IDS中
            WHILE V_ID IS NOT NULL DO 
            
                         SET STR_IDS= CONCAT(STR_IDS,',',V_ID); 
                         #GROUP_CONCAT(),FIND_IN_SET()函数介绍:https://www.cnblogs.com/longzhongren/p/4775293.html
                         #需要注意的是GROUP_CONCAT()可能会数据不全,解决方式:https://blog.csdn.net/dream_broken/article/details/69554303
                         #这条SQL的意思是从组织表中查找PID存在V_ID字符串中的记录行,                 
                         #将其ID组成新的字符串赋值给V_ID,即根据一个ID,获取该ID  下的所有子ID的集合
                         SELECT GROUP_CONCAT(ID) INTO V_ID FROM organization WHERE FIND_IN_SET(PID,V_ID) > 0 ;  
                 
            END WHILE;
            
            #如果仅仅想通过,传入ID,获取其所有的子节点,则取消注释下面这段代码,同时删掉下面的代码即可
            #SELECT * from organization where FIND_IN_SET(ID,STR_IDS);
            
            #打印ID的结果
            #SELECT STR_IDS;
            #@STR_IDS_LENGTH是获取要分割字符串[我这里是逗号]根据符号分割后的数组长度
            SET @STR_IDS_LENGTH = (LENGTH(STR_IDS) - LENGTH(REPLACE(STR_IDS,',',''))) + 1;

            #SELECT @STR_IDS_LENGTH;
            WHILE V_INDEX < @STR_IDS_LENGTH DO
                    #索引加1
                    SET V_INDEX = V_INDEX + 1;
                    #根据索引截取ID
                    SET @ID = REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(STR_IDS,',',V_INDEX)),',',1));
                    #SELECT @ID;
                    #插入关系表,需要注意的是游标变量就是父ID,STR_IDS是该父ID下的所有子ID的集合
                    INSERT INTO organization_concern (R_ID,R_PID) VALUES (@ID,S_ID);
                    
            END WHILE;        
            
            
        #终止循环
        END Loop OUTER_LOOP;
        
        #去掉表中R_ID与R_PID一致的数据,一个节点的父节点不能是自己
        DELETE FROM organization_concern WHERE R_ID = R_PID;
        
        #将根节点插入关系表中where条件根据自己的表设计来判断,一般是NULL或者是0等等
        INSERT INTO organization_concern (R_ID,R_PID) SELECT ID,PID FROM organization where PID IS NULL;
        
        #关闭游标
        CLOSE OUTER_CUR;
    
END

我们创建了一个 名叫 DATA_CONVERSION 的存储过程,他的作用就是为关系表写入数据,他通过遍历当前所有节点,并将每个节点的所有子节点记录下来,写入到关系表中

需要注意的是,每次组织表由变动时,都需要调用这个存储过程,这个存储过程会清空关系表并重新写数据,那么为什么不修改这张关系表呢?因为组织表如果发生了改变,其变化

是不可控的[比如一个人从A部门调到B部门,或者一个升职,一个人降职,一个部门取消了等等],修改起来也十分复杂,倒不如重新生成,而且组织表一般都是读多写少的操作,所以我

们只用关心读取的性能就好了!

那么我们现在关系表中有了数据,再进行读取数据就方便多了!

直接连接查询就好了!

 

 

 

这样性能是不是比递归查询性能好很多呢?

 

posted @ 2018-08-17 15:26  陈无问  阅读(2495)  评论(0编辑  收藏  举报