• 数据库递归树形查询优化


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

    这个时候我们在查询等级关系时,第二种表设计的查询方式是: 使用 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部门,或者一个升职,一个人降职,一个部门取消了等等],修改起来也十分复杂,倒不如重新生成,而且组织表一般都是读多写少的操作,所以我

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

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

    直接连接查询就好了!

     

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

  • 相关阅读:
    jQuery源码分析-01总体架构
    JS框架的一些小总结
    jquery validate表单验证插件
    jQuery $.extend()用法总结
    详解;(function ($,window,document,undefined){...})(jQuery,window,document);
    (function(){... })();
    同源策略 JSONP(实践篇)
    **字符集--发展史
    Django X 和 druid
    Django xadmin的使用 (三)
  • 原文地址:https://www.cnblogs.com/kanyun/p/9493509.html
Copyright © 2020-2023  润新知