关于树结构的数据的权限控制的算法

树结构的权限控制分两种:1、逐层加载;2、一次性加载

一、逐层加载

涉及的表结构

表名 T_PLAN

表字段

字段类型

是否必

须字段

说明

ID

VARCHAR2(50)

Y

主键

PARENT_ID

VARCHAR2(50)

Y

父项节点ID,默认根节点的父节点ID=’-1’

TREEPATH

VARCHAR2(4000)

Y

从根节点到当前节点的ID路径,用“/”号分隔

LEVEL_SN

NUMBER

当前节点所在整个树结构的层次

备注说明:

LEVEL_SN 字段可以不需要,因为可以通过 TREEPATH的值来计算出来

示例数据如下:

ID

PARENT_ID

TREEPATH

LEVEL_SN

A

-1

A

1

A1

A

A/A1

2

A2

A

A/A2

2

A1-1

A1

A/A1/A1-1

3

A1-2

A1

A/A1/A1-2

3

LEVEL_SN字段可以通过 LENGTH(TREEPATH)-LENGTH(REPLACE(TREEPATH,’/’,NULL) 获取

也可以通过 REGEXP_COUNT(TREEPATH,’/’)获取,但此函数必须在11.2版本之后才能正常使用,建议还是用第一种方式得到

权限表:T_POWER

表字段

字段类型

说明

ID

VARCHAR2(50)

主键

PLAN_ID

VARCHAR2(50)

树结构表T.ID

USER_ID

VARCHAR2(50)

用户id

表T_PLAN 与表T_POWER 的关系是一对多关系

  1. 逐层加载的算法

逐层加载,顾名思义,就是每层每层的加载数据,通过传递PARENT_ID=当前节点id 来获取当前节点的下层节点数据

参数

L_PARENT_ID = 当前需要展开的节点id

L_POLICY_FLAG=伪列(值是枚举类型  DOWN_UP 或者UP_DOWN )

DOWN_UP 表示 节点(A)是 因为下层节点(B)有权限看到,为了显示完整的树结构,才需要显示的节点,本身节点(A)是无权看到的

UP_DOWN 表示 节点(A)本身是有权限看到的,那么按树的权限规则,节点(A)下属的所有子节点都应该可以看到,那么节点(A)在进行展开的时候,就不需要与权限表有关系运算了,省略权限表的参与,减少表与表的关联

L_LEVEL_SN=当前节点在整个树的层次,默认根节点的层次=1

L_USER_ID=当前登录人

第一层 根节点

参数  (L_PARENT_ID, L_POLICY_FLAG, L_LEVEL_SN)

规则: 根节点默认所有人都能看见,但需要创建一个伪列,用来标识节点的获取通道

语法:

SELECT  M.ID,M.PARENT_ID,

         M.TREEPATH,M.LEVEL_SN,

         CASE WHEN P.USER IS NULL THEN 'DOWN_UP' ELSE 'UP_DOWN' END   POLICY_FLAG

         FROM T_PLAN M,T_POWER P

  WHERE M.PARENT_ID = '-1'   --L_PARENT_ID

  AND P.USER_ID = '登录人ID'  -- L_USER_ID

  AND T.ID=P.PLAN_ID(+)

递归的节点

 参数  (PARENT_ID,POLICY_FLAG,LEVEL_SN)

 规则  1.判断页面传递过来的 POLICY_FLAG参数值,如果=UP_DOW 那么,表明展开的节点对登录人而言是有权限看见的,后续的所有节点都可以直接查询T_PLAN表了

       如果=DOWN_UP那么表明展开的节点对登录人来说,是因为更底层的节点的权限反向查询出来的,再度展开此节点的时候,需要关联权限表T_POWER进行判断。

IF  POLICY_FLAG =UP_DOWN THEN

SELECT

 PM.ID, PM.PARENT_ID, PM.TREEPATH,

  PM.LEVEL_SN,'UP_DOWN' POLICY_FLAG

 FROM  T_PLAN  PM

 WHERE PM.LEVEL_SN= 'L_LEVEL_SN+1 '   //   =当前节点的层次+1 ,注意,

 AND PM.PARENT_ID='L_PARENT_ID'    // --当前节点ID  

1.此处 PM.LEVEL_SN= ' L_LEVEL_SN+1 ' 与   PM.PARENT_ID='L_PARENT_ID'  条件的组合其实只需要  PM.PARENT_ID='L_PARENT_ID'  即可, PM.LEVEL_SN= 'L_LEVEL_SN+1 '条件在此处可以拿掉

2.注意此处的伪列 POLICY_FLAG 是固定的值 UP_DOWN 为了从当前节点把展开下层的权限一直继承下去

 3. ORDER BY 条件 如果树结构有排序规则,那么请增加排序字段

ELSE

WITH T AS (

SELECT  /*DISTINCT*/  REGEXP_SUBSTR(TREEPATH,'[^/]+',1, L_LEVEL_SN+1)  SHOW_ID,

DECODE(REGEXP_SUBSTR(TREEPATH,'[^/]+',1,L_LEAVEL_SN+1),J.ID,'UP_DOWN','DOWN_UP') POLICY_FLAG,

ROW_NUMBER() OVER(PARTITION BY REGEXP_SUBSTR(TREEPATH,'[^/]+',1,L_LEVEL_SN+1) ORDER BY NULL)  SN

FROM T_POWER  X,T_PLAN J

WHERE  X.PLAN_ID=J.ID

AND X.USER_ID='USER_ID'  //当前用户

AND J.LEVEL_SN > ='L_LEVEL_SN+1'  //当前节点层级+1

)

SELECT

 PM.ID, PM.PARENT_ID, PM.TREEPATH,

  PM.LEVEL_SN,T.POLICY_FLAG

FROM T,T_PLAN PM

WHERE PM.LEVEL_SN=L_LEVEL_SN+1

 AND T.SN = 1

 AND PM.PARENT_ID='L_PARENT_ID'

AND PM.ID=SHOW_ID  

ORDER BY  

  1. 此处说明下 regexp_substr(treepath,’[^/]+’,1,l_level_sn +1) 函数意思是
       treepath=’a/b/c/d/e’ ;  l_level_sn+1=3  那么函数返回的值是 c,依次类推
  2. 伪列的值是需要判断出来的,
        

数据

伪列

Treepath

 伪列的规则

a

down_up

a

 b

down_up

a/b

   c

down_up

a/b/c

     c1

a/b/c/c1

     c2

up_down

a/b/c/c2

        c21

up_down

a/b/c/c2/c21

        c22

up_down

a/b/c/c2/c22

        红色 c2 表示登录人有权限的节点

       如上的数据格式,那么能看到的数据应该是除去 蓝色c1 ,其它都应该看到,每个数据的伪列值

DECODE( REGEXP_SUBSTR(TREEPATH,'[^/]+',1,L_LEAVEL_SN+1),

J.ID,'UP_DOWN',

'DOWN_UP'

)

        需要仔细读懂此处的代码含义; J.ID是登录人拥有的权限节点,根据路径和需要展示的层级获取显示的节点id,判断显示的节点id是否=J.ID,如果等于,那么显示的节点就是拥有权限的节点,给予 UP_DOWN的值,否则不是,还是需要给予 DOWN_UP值

  1. 此处使用了分析函数,

ROW_NUMBER() OVER(PARTITION BY REGEXP_SUBSTR(TREEPATH,'[^/]+',1,L_LEVEL_SN+1) ORDER BY NULL)  SN

与后面的条件 AND T.SN = 1   用来排除重复的节点id, 也可以直接使用distinct进行排重

END IF;

二、一次性加载

一次性加载的意思是,通过sql直接查询出登录入拥有的数据,然后向下显示所有孩子节点,向上显示父亲节点, 数据逻辑很简单,要写出高效率的sql确不是很容易的,

这里面使用了oracle的model函数,

Model函数的用法和讲解见model语法

这里做具体的解法说明

原理:

    

BZ:对当前节点是否拥有权限 1 表示是 0 表示否
序号 是ROW_NUMBER() OVER (ORDER BY  TREEPATH ) SN  的值

数据

treepath

BZ

序号

FLAG_DOWN

DOWN

FLAG_ DOWN的取数规则

a

a

0

1

null

0

X=(当前序号-1)的flag_down的值

Y= 当前记录bz的值

Z=当前记录的treepath值

1.如果 Y=1 and x is null 那么取 z

2.如果y=1 and x is not null那么取
(如果 z包括x 那么取 x 否则取 z)

3.如果 x is not null and y=0 那么取 x

4.其它情况都取 null值

5.down取数规则:
按treepath升序排序:如果当前treepath包括在当前flag_dow里,那么取1 否则取0
按treepath将序排序:如果当前flag_dow包括在当前treepath里,那么取1 否则取0

 b

a/b

0

2

null

0

   c

a/b/c

0

3

null

0

     c1

a/b/c/c1

0

4

null

0

     c2

a/b/c/c2

1

5

a/b/c/c2

1

        c21

a/b/c/c2/c21

0

6

a/b/c/c2

1

        c22

a/b/c/c2/c22

0

7

a/b/c/c2

1

BZ:对当前节点是否拥有权限 1 表示是 0 表示否
序号 是ROW_NUMBER() OVER (ORDER BY  TREEPATH ) SN  的值

数据

treepath

BZ

序号

FLAG_ UP

UP

FLAG_ UP 的取数规则

a

a

0

7

a/b/c/c2

1

X=(当前序号+1)的flag_down的值

Y= 当前记录bz的值

Z=当前记录的treepath值

1.如果 Y=1 and x is null 那么取 z

2.如果y=1 and x is not null那么取
(如果 z包括x 那么取 x 否则取 z)

3.如果 x is not null and y=0 那么取 x

4.其它情况都取 null值

5.down取数规则:

按treepath将序排序:如果当前flag_dow包括在当前treepath里,那么取1 否则取0

 b

a/b

0

6

a/b/c/c2

1

   c

a/b/c

0

5

a/b/c/c2

1

     c1

a/b/c/c1

0

4

a/b/c/c2

1

     c2

a/b/c/c2

1

3

a/b/c/c2

1

        c21

a/b/c/c2/c21

0

2

0

        c22

a/b/c/c2/c22

0

1

0

WITH T AS (

SELECT * FROM T_TEST    

  MODEL 

  ---此处的排序规则定义了从子节点开始进行深度优先的反向树排序

  DIMENSION BY(ROW_NUMBER() OVER (ORDER BY TREE_PATH ) SN )

  MEASURES(

          ID,PARENT_ID,NAME,BZ,TREE_PATH,

          CAST (NULL AS VARCHAR2(2)) HAS_CHILD,    --判断当前节点是否有叶子节点

          CAST (NULL AS VARCHAR2(2000)) FLAG_DOWN, --用于判断向下追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) DOWN,

          CAST (NULL AS VARCHAR2(2000)) FLAG_UP,  --用于判断向上追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) UP   

              )

   --关建字 Sequential =让数据库的单元格运算先后关系以SQL的写法顺序执行,不需要oracle自己决定先后顺序

    RULES Sequential order (

--向下级寻找节点  

FLAG_DOWN[ANY] =   --上一个节点没有权限,当前节点有权限,获取当前节点权限

                CASE   WHEN  FLAG_DOWN[CV()- 1] IS NULL      AND NVL(BZ[CV()],0)=1    THEN  TREE_PATH[CV()]

                  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                       WHEN  FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN 

                                            --如果当前tree_path包含了上一个节点的flag_down值 那么取上一个节点flag_down值

                                            CASE WHEN INSTR(TREE_PATH[CV()],FLAG_DOWN[CV()-1])>0 THEN FLAG_DOWN[CV()-1]

                                            ELSE  TREE_PATH[CV()] END

                  --上一个节点有权限继承,当前节点无权限  继承上一节点权限   

                       WHEN FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0  THEN  

                                   FLAG_DOWN[CV()- 1]

                       ELSE NULL END                         

,DOWN[ANY]  = CASE WHEN INSTR(TREE_PATH[CV()],FLAG_DOWN[CV()])>0 THEN 1 ELSE 0 END

--向上寻找节点,注意此处是倒序输出的

,FLAG_UP[ANY] ORDER BY TREE_PATH DESC  

                       --上一个节点没有权限,当前节点有权限,获取当前节点权限

              = CASE   WHEN FLAG_UP[CV()+ 1] IS NULL AND  NVL(BZ[CV()],0)=1  THEN  TREE_PATH[CV()]

                       --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN

                                            --如果当前tree_path包含了上一个节点的flag_down值 那么取上一个节点flag_down值                           

                                            CASE WHEN INSTR(TREE_PATH[CV()],FLAG_UP[CV()+1])>0 THEN FLAG_UP[CV()+1]

                                            ELSE  TREE_PATH[CV()] END   

                       --上一个节点有权限继承,当前节点无权限  继承上一节点权限                          

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0  THEN  

                                   FLAG_UP[CV()+ 1]

                       ELSE NULL END    

,UP[ANY]  = CASE WHEN INSTR(FLAG_UP[CV()],TREE_PATH[CV()])>0 THEN 1 ELSE 0 END

--表示当前节点是下一个节点的父亲节点

,HAS_CHILD[ANY]=CASE WHEN ID[CV()]=PARENT_ID[CV()+1] THEN 'Y' ELSE 'N' END 

)

)

SELECT * FROM T  WHERE DOWN+UP>0;

常规树权限查询与model树权限查询的测试场景

三、性能测试

测试数据

执行t_task_info.sql

清除数据库的共享池和数据缓冲区

ALTER SYSTEM FLUSH SHARED_POOL;

ALTER SYSTEM FLUSH BUFFER_CACHE;

数据量较少

--注意看下每个SQL的执行计划

没有tree_path的语法

select * from t_test connect by prior id=parent_id start with id in (select id from t_test  where bz =1)

union 

select * from t_test connect by prior parent_id =id start with id in (select id from t_test  where bz =1)

;

有tree_path的语法

select * from t_test t  where exists  (select 1 from t_test z where z.bz =1 and instr(z.tree_path,t.name)>0)

union  

select * from t_test t1 where  exists  (select 1 from t_test z  where z.bz =1 and instr(t1.tree_path,z.name)>0)

;

--验证下 对treepath的排序,是否影响整个树结构的显示

select lpad(' ',(level-1)*10,' ')||name ,name,id,parent_id,tree_path,

sys_connect_by_path(name,'/') scbp from T_TEST

connect by prior id=parent_id start with parent_id='-1'

;

model解法

WITH T AS (

SELECT * FROM T_TEST    

  MODEL 

  ---此处的排序规则定义了从子节点开始进行深度优先在广度的反向树排序

  DIMENSION BY(ROW_NUMBER() OVER (ORDER BY TREE_PATH ) SN )

  MEASURES(

          ID,PARENT_ID,NAME,BZ,TREE_PATH,

          CAST (NULL AS VARCHAR2(2)) HAS_CHILD,    --判断当前节点是否有叶子节点

          CAST (NULL AS VARCHAR2(2000)) FLAG_DOWN, --用于判断向下追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) DOWN,

          CAST (NULL AS VARCHAR2(2000)) FLAG_UP,  --用于判断向上追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) UP   

              )

   --关建字 Sequential =让数据库的单元格运算先后关系以SQL的写法顺序执行,不需要oracle自己决定先后顺序

    RULES Sequential order (

--向下级寻找节点  

FLAG_DOWN[ANY] =   --上一个节点没有权限,当前节点有权限,获取当前节点权限

                CASE   WHEN  FLAG_DOWN[CV()- 1] IS NULL      AND NVL(BZ[CV()],0)=1    THEN  TREE_PATH[CV()]

                  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                       WHEN  FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN 

                                            --如果当前tree_path包含了上一个节点的flag_down值 那么取上一个节点flag_down值

                                            CASE WHEN INSTR(TREE_PATH[CV()],FLAG_DOWN[CV()-1])>0 THEN FLAG_DOWN[CV()-1]

                                            ELSE  TREE_PATH[CV()] END

                  --上一个节点有权限继承,当前节点无权限  继承上一节点权限   

                       WHEN FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0  THEN  

                                   FLAG_DOWN[CV()- 1]

                       ELSE NULL END                         

,DOWN[ANY]  = CASE WHEN INSTR(TREE_PATH[CV()],FLAG_DOWN[CV()])>0 THEN 1 ELSE 0 END

--向上寻找节点,注意此处是倒序输出的

,FLAG_UP[ANY] ORDER BY TREE_PATH DESC  

                       --上一个节点没有权限,当前节点有权限,获取当前节点权限

              = CASE   WHEN FLAG_UP[CV()+ 1] IS NULL AND  NVL(BZ[CV()],0)=1  THEN  TREE_PATH[CV()]

                       --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN

                                            --如果当前tree_path包含了上一个节点的flag_down值 那么取上一个节点flag_down值                           

                                            CASE WHEN INSTR(TREE_PATH[CV()],FLAG_UP[CV()+1])>0 THEN FLAG_UP[CV()+1]

                                            ELSE  TREE_PATH[CV()] END   

                       --上一个节点有权限继承,当前节点无权限  继承上一节点权限                          

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0  THEN  

                                   FLAG_UP[CV()+ 1]

                       ELSE NULL END    

,UP[ANY]  = CASE WHEN INSTR(FLAG_UP[CV()],TREE_PATH[CV()])>0 THEN 1 ELSE 0 END

--表示当前节点是下一个节点的父亲节点

,HAS_CHILD[ANY]=CASE WHEN ID[CV()]=PARENT_ID[CV()+1] THEN 'Y' ELSE 'N' END 

)

)

SELECT * FROM T  WHERE DOWN+UP>0

;

数据量很多,但符合条件的记录很少

select count(1) from t_task_info

select * from t_task_info

where task_name in ('工作包13233','工作包11113','工作包253');

没有 TASK_PATH 的语法

select * from t_task_info connect by prior id=parent_id

start with id in 

(select id from t_task_info where task_name in ('工作包13233','工作包11113','工作包253'))

union 

select * from t_task_info connect by prior parent_id =id

start with id in 

( select id from t_task_info where task_name in ('工作包13233','工作包11113','工作包253'))

 ;  

有tree_path的语法

select t.* from t_task_info t,sys_user su where t.user_id=su.id(+)   and exists 

  (select 1 from t_task_info where ( task_name in ('工作包13233','工作包11113','工作包253')) and instr(TASK_PATH,t.TASK_PATH)>0)

union  

select t1.* from t_task_info t1,sys_user su where t1.user_id=su.id(+)  and  exists 

  (select 1 from t_task_info  where ( task_name in ('工作包13233','工作包11113','工作包253')) and instr(t1.TASK_PATH,id)>0)

  ;

  

MODEL语法

WITH T AS (

SELECT  ID,PARENT_ID,TASK_NAME,TASK_PATH,HAS_CHILD,DOWN,UP FROM t_task_info i,sys_user su where user_id=su.id(+)    

  MODEL 

  DIMENSION BY(ROW_NUMBER() OVER (ORDER BY TASK_PATH ) SN )  ---此处的排序规则定义了从子节点开始进行深度优先在广度的反向树排序

  MEASURES(

          i.ID,i.PARENT_ID,i.TASK_NAME,i.TASK_PATH,

          CASE WHEN TASK_NAME in ('工作包13233','工作包11113','工作包253') THEN 1 ELSE 0 END  BZ,

          CAST (NULL AS VARCHAR2(2)) HAS_CHILD,    --判断当前节点是否有叶子节点

          CAST (NULL AS VARCHAR2(2000)) FLAG_DOWN, --用于判断向下追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) DOWN,

          CAST (NULL AS VARCHAR2(2000)) FLAG_UP,  --用于判断向上追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) UP   

              )

    RULES Sequential order (

--向下级寻找节点  

FLAG_DOWN[ANY] =CASE   WHEN  FLAG_DOWN[CV()- 1] IS NULL      AND NVL(BZ[CV()],0)=1  THEN  TASK_PATH[CV()] --上一个节点没有权限,当前节点有权限,获取当前节点权限

                       WHEN  FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                                            CASE WHEN INSTR(TASK_PATH[CV()],FLAG_DOWN[CV()-1])>0 THEN FLAG_DOWN[CV()-1]

                                            ELSE  TASK_PATH[CV()] END   

                       WHEN FLAG_DOWN[CV()- 1] IS NOT NULL   AND NVL(BZ[CV()],0)=0  THEN  --上一个节点有权限继承,当前节点无权限  继承上一节点权限

                                   FLAG_DOWN[CV()- 1]

                       ELSE NULL END                         

,DOWN[ANY]  = CASE WHEN INSTR(TASK_PATH[CV()],FLAG_DOWN[CV()])>0 THEN 1 ELSE 0 END

--向上寻找节点,注意此处是倒序输出的

,FLAG_UP[ANY] ORDER BY TASK_PATH DESC  

              = CASE   WHEN FLAG_UP[CV()+ 1] IS NULL      AND  NVL(BZ[CV()],0)=1  THEN  TASK_PATH[CV()] ----上一个节点没有权限,当前节点有权限,获取当前节点权限

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1   THEN  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                                            CASE WHEN INSTR(TASK_PATH[CV()],FLAG_UP[CV()+1])>0 THEN FLAG_UP[CV()+1]

                                            ELSE  TASK_PATH[CV()] END   

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0   THEN  --上一个节点有权限继承,当前节点无权限  继承上一节点权限

                                   FLAG_UP[CV()+ 1]

                       ELSE NULL END    

,UP[ANY]  = CASE WHEN INSTR(FLAG_UP[CV()],TASK_PATH[CV()])>0 THEN 1 ELSE 0 END

,HAS_CHILD[ANY]=CASE WHEN ID[CV()]=PARENT_ID[CV()+1] THEN 'Y' ELSE 'N' END --表示当前节点是下一个节点的父亲节点

)

)

SELECT * FROM T  WHERE DOWN+UP>0

数据量很多,但符合条件的记录很多

select count(*) from t_task_info where  TASK_NAME like '工作包1%' or TASK_NAME like '工作包6%' or TASK_NAME like '工作包8%' or TASK_NAME like '工作包1%'

没有 TASK_PATH 的语法 

select * from t_task_info connect by prior id=parent_id

start with id in 

(select id from t_task_info where task_name like '工作包1%' or task_name like '工作包6%' or task_name like '工作包8%' or task_name like '工作包1%')

union 

select * from t_task_info connect by prior parent_id =id

start with id in 

( select id from t_task_info where task_name like '工作包1%' or task_name like '工作包6%' or task_name like '工作包8%' or task_name like '工作包1%') 

;

有tree_path的语法

select t.* from t_task_info t,sys_user su where t.user_id=su.id(+)   and exists 

  (select 1 from t_task_info where (task_name like '工作包1%' or task_name like '工作包6%' or task_name like '工作包8%' or task_name like '工作包1%') and instr(TASK_PATH,t.TASK_PATH)>0)

union  

select t1.* from t_task_info t1,sys_user su where t1.user_id=su.id(+)  and  exists 

  (select 1 from t_task_info  where (task_name like '工作包1%' or task_name like '工作包6%' or task_name like '工作包8%' or task_name like '工作包1%') and instr(t1.TASK_PATH,id)>0)

  ;

  

MODEL语法

WITH T AS (

SELECT  ID,PARENT_ID,TASK_NAME,TASK_PATH,HAS_CHILD,DOWN,UP FROM t_task_info i,sys_user su where user_id=su.id(+)    

  MODEL 

  DIMENSION BY(ROW_NUMBER() OVER (ORDER BY TASK_PATH ) SN )  ---此处的排序规则定义了从子节点开始进行深度优先在广度的反向树排序

  MEASURES(

          i.ID,i.PARENT_ID,i.TASK_NAME,i.TASK_PATH,

          CASE WHEN TASK_NAME like '工作包1%' or TASK_NAME like '工作包6%' or TASK_NAME like '工作包8%' or TASK_NAME like '工作包1%' THEN 1 ELSE 0 END  BZ,

          CAST (NULL AS VARCHAR2(2)) HAS_CHILD,    --判断当前节点是否有叶子节点

          CAST (NULL AS VARCHAR2(2000)) FLAG_DOWN, --用于判断向下追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) DOWN,

          CAST (NULL AS VARCHAR2(2000)) FLAG_UP,  --用于判断向上追溯树的权限节点tree_path

          CAST (NULL AS NUMBER) UP   

              )

    RULES Sequential order (

--向下级寻找节点  

FLAG_DOWN[ANY] =CASE   WHEN  FLAG_DOWN[CV()- 1] IS NULL      AND NVL(BZ[CV()],0)=1  THEN  TASK_PATH[CV()] --上一个节点没有权限,当前节点有权限,获取当前节点权限

                       WHEN  FLAG_DOWN[CV()- 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1  THEN  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                                            CASE WHEN INSTR(TASK_PATH[CV()],FLAG_DOWN[CV()-1])>0 THEN FLAG_DOWN[CV()-1]

                                            ELSE  TASK_PATH[CV()] END   

                       WHEN FLAG_DOWN[CV()- 1] IS NOT NULL   AND NVL(BZ[CV()],0)=0  THEN  --上一个节点有权限继承,当前节点无权限  继承上一节点权限

                                   FLAG_DOWN[CV()- 1]

                       ELSE NULL END                         

,DOWN[ANY]  = CASE WHEN INSTR(TASK_PATH[CV()],FLAG_DOWN[CV()])>0 THEN 1 ELSE 0 END

--向上寻找节点,注意此处是倒序输出的

,FLAG_UP[ANY] ORDER BY TASK_PATH DESC  

              = CASE   WHEN FLAG_UP[CV()+ 1] IS NULL      AND  NVL(BZ[CV()],0)=1  THEN  TASK_PATH[CV()] ----上一个节点没有权限,当前节点有权限,获取当前节点权限

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=1   THEN  --上一个节点有权限继承,当前节点也有权限 需要判断权限的继承

                                            CASE WHEN INSTR(TASK_PATH[CV()],FLAG_UP[CV()+1])>0 THEN FLAG_UP[CV()+1]

                                            ELSE  TASK_PATH[CV()] END   

                       WHEN FLAG_UP[CV()+ 1] IS NOT NULL  AND NVL(BZ[CV()],0)=0   THEN  --上一个节点有权限继承,当前节点无权限  继承上一节点权限

                                   FLAG_UP[CV()+ 1]

                       ELSE NULL END    

,UP[ANY]  = CASE WHEN INSTR(FLAG_UP[CV()],TASK_PATH[CV()])>0 THEN 1 ELSE 0 END

,HAS_CHILD[ANY]=CASE WHEN ID[CV()]=PARENT_ID[CV()+1] THEN 'Y' ELSE 'N' END --表示当前节点是下一个节点的父亲节点

)

)

SELECT * FROM T  WHERE DOWN+UP>0

四、树结构的汇总

解法:利用树的深度和广度的遍历,建立主和关联的两个model记录,实现依次的数据累计

select id,parent_id,name,sal,tree_path,lpad(' ',regexp_count(tree_path,'/')*10,' ')||name level_name

,row_number() over (order by length(tree_path)-length(replace(tree_path,'/')) desc,parent_id desc ,name desc ) lev_sn

,row_number() over (order by tree_path desc )sn

from t_test

;

关于树结构的数据的权限控制的算法_第1张图片

关于树结构的数据的权限控制的算法_第2张图片

Connect By 解法 

 with t as (

select connect_by_root(name) g_name,sal

from t_test connect by prior id=parent_id

)

select g_name,sum(sal) from t group by g_name;

Tree_Path 解法 

   select regexp_substr(tree_path,'[^/]+',1,t.lev) g_name

    ,sum(sal)  from t_test ,

    (select level lev from dual connect by level<=20) t

   where regexp_count(tree_path,'/')>=t.lev-1

   group by regexp_substr(tree_path,'[^/]+',1,t.lev);

Model解法

select id,parent_id,name,sal,sum_total,tree_path,lev_sn,sn

from t_test

model

  REFERENCE over_result ON  ( --创建以深度遍历树的记录model,用来获取 越层级 的节点sn值

    SELECT  parent_id over_parent_id,row_number() over (order by tree_path desc ) over_sn --深度遍历优先序列

    ,row_number() over (order by length(tree_path)-length(replace(tree_path,'/')) desc,parent_id desc ,name desc ) over_lev_sn --广度遍历序列

       FROM t_test

    )

    DIMENSION BY ( over_sn)

    MEASURES (over_parent_id,over_lev_sn ) IGNORE NAV --- ignore nav 表示存在不满足的情况用0替换

MAIN  main_model -- 主的model函数申明

dimension by (   -- 广度遍历优先的 序列

 row_number() over (order by length(tree_path)-length(replace(tree_path,'/')) desc,parent_id desc ,name desc ) lev_sn

)

measures (

id,parent_id,name,sal

,cast (null as number(12,2)) sum_sal   --创建的一个累计字段

,cast (null as number(12,2)) sum_total --当前节点的统计(包括所有子节点)

,tree_path

,row_number() over (order by tree_path desc ) sn --深度遍历序列

)

rules Sequential order (

--sum_sal 标识 按树结构从底层节点依次进行的累计汇总                                        

sum_sal[any]= case when parent_id[cv()-1]=parent_id[cv()] then --当前节点和上一个节点是否同一个父节点

                           --从关联模型数据中取出 主模型数据的当前节点的上一个节点

                           --判断 当前节点id是否等于关联模型中 对应当前节点的上一个节点父项(如果是 进行sal的累计=当前节点sal+当前节点的最后子节点sum_sal,否则当前节点sal)

                           case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                                                                          else sal[cv()]

                           end + nvl(sum_sal[cv()-1] ,0)  --此处进行sal的累计                   

                   else case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                        else sal[cv()] end 

                   end

--sum_total 标识 每个节点的汇总累计

--汇总累计=当前节点的值+当前节点的最后一个子节点的 累计值                   

,sum_total[any]=    case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                    else sal[cv()] end                   

);

大数据量的效率测试

数据量较大(1000-10000)

Connect by scott

清除数据库的共享池和数据缓冲区

ALTER SYSTEM FLUSH SHARED_POOL;

ALTER SYSTEM FLUSH BUFFER_CACHE;

tree_path

with t as (

  select regexp_substr(task_path,'[^/]+',1,t.lev) g_name

    ,sum(task_no)  from t_task_info ,

    (select level lev from dual connect by level<=20) t

   where regexp_count(task_path,'/')>=t.lev-1 

   group by regexp_substr(task_path,'[^/]+',1,t.lev)

)

select * from t where g_name='40288048411a331c01411a5f9bfb5396'

;

connect by

with t as (

select connect_by_root(id) g_name,task_no sal

from t_task_info connect by prior id=parent_id

) ,

t1 as (

select g_name,sum(sal) sum_sal

from t group by g_name )

select *

from t1 where g_name='40288048411a331c01411a5f9bfb5396'

;

model

with t as (

select id,parent_id,lpad(' ',regexp_count(task_path,'/')*10,' ')||name,sal,sum_total,task_path,lev_sn,sn

from t_task_info

model

  REFERENCE over_result ON  (

    SELECT  parent_id over_parent_id,row_number() over (order by task_path desc ) over_sn

    ,row_number() over (order by length(task_path)-length(replace(task_path,'/')) desc,parent_id desc ,id desc ) over_lev_sn

       FROM t_task_info

    )

    DIMENSION BY ( over_sn)

    MEASURES (over_parent_id,over_lev_sn ) IGNORE NAV

MAIN  main_model

dimension by ( row_number() over (order by length(task_path)-length(replace(task_path,'/')) desc,parent_id desc ,id desc ) lev_sn )

measures (

id,parent_id,task_name name,task_no sal

,cast (null as number(12,2)) sum_sal

,cast (null as number(12,2)) sum_total

,task_path

,row_number() over (order by task_path desc ) sn

)

rules Sequential order (                                        

sum_sal[any]= case when parent_id[cv()-1]=parent_id[cv()] then 

                           case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                                                                          else sal[cv()]

                           end + nvl(sum_sal[cv()-1] ,0)                     

                   else case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                        else sal[cv()] end 

                   end

,sum_total[any]=    case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                                                                          else sal[cv()]

                    end                                         

)

) select * from t where id='40288048411a331c01411a5f9bfb5396'

数据量很大(>10000)

Connect by  scott

connect by 

with t as (select  connect_by_root(i.id) g_id ,i.task_no  from pm_task_info  i

where  i.pm_project_id='4028809540ddb4830140ddde7a5b009c'

connect by prior i.id=i.parent_id

),

t1 as (

select g_id,sum(task_no) from t   group by g_id

)

select * from t1  where g_id='4028809540e1fe470140e22db8182537'

;

tree_path

 with t as (

 select regexp_substr(task_path,'[^/]+',1,t.lev) g_id

    ,sum(task_no)  from pm_task_info ,

    (select level lev from dual connect by level<=20) t

   where length(task_path)-length(replace(task_path,'/'))>=t.lev-1 

   and pm_project_id='4028809540ddb4830140ddde7a5b009c'

   group by regexp_substr(task_path,'[^/]+',1,t.lev)

   )

   select * from t where g_id='4028809540e1fe470140e22db8182537'

   ;

model

with t as (

select id,parent_id,lpad(' ',(length(task_path)-length(replace(task_path,'/')))*10,' ')||name,sal,sum_total,task_path,lev_sn,sn

from pm_task_info  where pm_project_id='4028809540ddb4830140ddde7a5b009c'

model

  REFERENCE over_result ON  (

    SELECT  parent_id over_parent_id,row_number() over (order by task_path desc ) over_sn

    ,row_number() over (order by length(task_path)-length(replace(task_path,'/')) desc,parent_id desc ,id desc ) over_lev_sn

       FROM pm_task_info  where pm_project_id='4028809540ddb4830140ddde7a5b009c'

    )

    DIMENSION BY ( over_sn)

    MEASURES (over_parent_id,over_lev_sn ) IGNORE NAV

MAIN  main_model

dimension by ( row_number() over (order by length(task_path)-length(replace(task_path,'/')) desc,parent_id desc ,id desc ) lev_sn )

measures (

id,parent_id,task_name name,task_no sal

,cast (null as number(12,2)) sum_sal

,cast (null as number(12,2)) sum_total

,task_path

,row_number() over (order by task_path desc ) sn

)

rules Sequential order (                                        

sum_sal[any]= case when parent_id[cv()-1]=parent_id[cv()] then 

                           case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                                                                          else sal[cv()]

                           end + nvl(sum_sal[cv()-1] ,0)                     

                   else case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                        else sal[cv()] end 

                   end

,sum_total[any]=    case when  over_parent_id[sn[cv()]-1]=id[cv()] then sal[cv()]+ nvl(sum_sal[ over_lev_sn[ sn[cv()]-1]  ],0)

                                                                          else sal[cv()]

                    end                                         

  )

)

select * from t where id='4028809540e1fe470140e22db8182537'

;

五、model函数介绍

这个函数一般不太常见,但确是非常有用的一个函数,基本上model可以完成所有函数的功能

语法定义如下

--MODEL:MODEL语句的关键字,必须,后面可以跟  partition by

--DIMENSION BY: 维度的意思,必须,而且必须是一个主键或者是组合主键。

--MEASURES:指定作为数组的列,可以定义出许多有规则的伪列

--RULES:对数组的各列进行各种操作规则的定义,特有的函数有 any,cv(),cv(维度字段)

先从简单的了解下model函数的特性:

自循环功能

select key, m_1  from dual

  model 

  dimension by(0 key)  --定义维度 列名=key 值等于0

  measures( cast(null as varchar2(100)) m_1 )  --定义一个度量 类型是 varchar2(100)  列名=m_1

  rules --规则约束

  iterate(5)  --定义自循环次数 =5 从 0 开始循环

  (m_1[0]= nvl(m_1 [0],'TEST')|| 'x'||'/'||iteration_number||'/')

关于树结构的数据的权限控制的算法_第3张图片


利用model的循环来实现阶层的算法

select * from dual

model 

dimension by (1 as c)

measures (1 as d)

rules iterate (5) --定义了循环5此,从 0开始  ITERATION_NUMBER FOR 0 TO 4

(

d[1]=d[1]*(ITERATION_NUMBER+1) --此处 ITERATION_NUMBER+1  for 1 to 5

)

关于树结构的数据的权限控制的算法_第4张图片

当然,此处不是要真的实现 阶乘 的算法,只是为了理解model函数的用法,

再看看如下的SQL

目的:根据emp表的 mgr和empno的关系 来显示 上级的ename和job

最直接最常用的语法就是

select x.empno,x.ename,x.job,x.mgr,y.ename,y.job from emp x,emp y

where x.mgr=y.empno(+)   ;

但这样的SQL的执行计划显示对EMP表进行了两次全表扫描关于树结构的数据的权限控制的算法_第5张图片

换成model函数执行下

select * from emp

model 

dimension by (empno)

measures ( ename,job,mgr

           ,cast(null as varchar2(20)) mgr_ename

           ,cast(null as varchar2(20)) mgr_job

          )

rules (

 mgr_ename[any]=ename[mgr[cv()]] 

 --cv()代表对当前行的维度值

 --mgr[cv()] 是获取当前维度下的mgr值 ,然后在对 mgr[cv()]进行维度的数据定位到度量ename 也就是当前ename的上级ename

,mgr_job[any]=job[mgr[cv()]]

)

再看看它的执行计划,如下图:关于树结构的数据的权限控制的算法_第6张图片

执行以下SQL,看看结果集,理解model 函数

--显示 部门,年份,当前年份汇总sal,上年汇总sal

with t as (select  deptno,to_char(emp.hiredate,'yyyy') year,sum(sal) sal from emp group by deptno,to_char(emp.hiredate,'yyyy'))

select deptno,year,sal,p_sal

from t

model

dimension by (deptno,year)

measures (sal,0 p_sal)

rules

(

p_sal[any,any]=sal[cv(),cv(year)-1]

);

--分组 group by deptno 合计

select   ename,sales from emp

model partition by (deptno)

dimension by (ename)

measures (sal sales)

rules

(

sales['合计']= sum(sales)[ cv(ename)='合计']

);

-- x =sal

-- y 只给deptno=30的赋予当前sum(sal)

-- z 显示 sum(sal) where deptno=20

-- m 汇总个部门的sum(sal)  

select   deptno,ename,sales,x,y,z,m from emp

model partition by (deptno)

dimension by (ename,deptno dep)

measures (sal sales,0 x,0 y,0 z,0 m)

rules

(

x[any,any]= sum(sales)[ cv(),cv()]

,y[any,any]=sales[cv(),30] --注意 此处是 30 可以不用sum, 而不是 cv()=30,cv()=30 存在多条记录

,z[any,any]=sum(sales) [any, cv()=20]

,m[any,any]=sum(sales) [any, any]

);

--部门号,年份,

--sum(sal) group by deptno,year

--sum(sal) group by deptno

--sum(sal) group by null

--sum(sal) group by year

--sum(sal) group by null

with t as (select  deptno,to_char(emp.hiredate,'yyyy') year,sum(sal) sal from emp group by deptno,to_char(emp.hiredate,'yyyy'))

select deptno,year,sal,p_sal,x,y,m

from t

model

dimension by (deptno,year)

measures (sal,0 p_sal ,0 x,0 y ,0 m)

rules

(

p_sal[any,any]=sum(sal)[cv(),cv() is not null ]  --sum(sal) group by deptno

,x[any,any]=sum(sal)[any,cv() is not null ]      --sum(sal) group by null

,y[any,any]=sum(sal)[cv() is not null,cv()]      --sum(sal) group by year

,m[any,any]=sum(sal)[cv() is not null,any ]      --sum(sal) group by null

-- cv() 中如果没有null 的记录那么 cv() is not null 等价与 any

);

用model函数产生 行转列

字符串='adfd,bere,cf234,4d54d'

select x,y, r,z

from dual

model

dimension  by (0 x)

measures ( cast ('adfd,bere,cf234,4d54d' as varchar2(200)) y

          ,cast(null as varchar2(1000)) z

          ,cast(null as varchar2(1000)) r --显示字符串列

) --申明一个字符串的伪列

rules iterate(10) --定义循环100次

--PRESENTV(cell,expr1,expr2)

--如果cell引用的记录在MODEL子句执行以前就存在,那么返回表达式expr1。如果这条记录不存在,则返回表达式expr2

   until ( presentv( y[instr(y[0],',',1,iteration_number+2)],0,1) = 0 ) --循环退出的条件

(  --对字符串进行循环截取操作  

y[iteration_number+1]=substr(y[iteration_number],instr(y[iteration_number],',',1)+1)

,r[any]=y[0]

,z[iteration_number]=nvl(substr(y[iteration_number],1,instr(y[iteration_number],',',1)-1),y[iteration_number])

,z[iteration_number+1]=y[iteration_number+1]

 )

用model函数产生  列转行

with t as (

select 'abc' x from dual

union all

select 'XTZ' from dual

union all

select 'IJM' from dual

union all

select 'KPI' from dual

)

select * from t  

model 

dimension by ( rownum sn)

measures( cast (x  as varchar2(1000)) x)

rules 

iterate (100)

until ( presentv( x[ iteration_number+1],1,0 )=0  )

(

x[0]=x[0]||','||x[iteration_number+1]

);

用model函数产生 交叉表格

select DEPTNO,CLERK_JOB,ANALYST_JOB,MANAGER_JOB,PRESIDENT_JOB,SALESMAN_JOB from emp

model partition by (deptno)

dimension by (empno,job)

measures (  ename,cast(null as varchar2(1000)) CLERK_JOB

                 ,cast(null as varchar2(1000)) ANALYST_JOB

                 ,cast(null as varchar2(1000)) MANAGER_JOB

                 ,cast(null as varchar2(1000)) PRESIDENT_JOB

                 ,cast(null as varchar2(1000)) SALESMAN_JOB

         )

rules(

 CLERK_JOB[ANY,ANY]= (ENAME[CV(),'CLERK'])

,ANALYST_JOB[ANY,ANY]=(ENAME[CV(),'ANALYST'])

,MANAGER_JOB[ANY,ANY]=(ENAME[CV(),'MANAGER'])

,PRESIDENT_JOB[ANY,ANY]=(ENAME[CV(),'PRESIDENT'])

,SALESMAN_JOB[ANY,ANY]=(ENAME[CV(),'SALESMAN'])

);

你可能感兴趣的:(技术专栏,java,jvm,数据库)