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

树结构的权限控制分两种: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

;

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||'/')


利用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

)

当然,此处不是要真的实现 阶乘 的算法,只是为了理解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表进行了两次全表扫描

换成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()]]

)

再看看它的执行计划,如下图:

执行以下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'])

);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/312072.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3的福音框架arco.design

前言&#xff1a; 在vue2于2023年底正式宣布不在维护&#xff0c;vue3使用越来越频繁的时刻&#xff0c;我们实现项目的辅助框架也越来越多。element, iview, antd 等经典框架继续风靡一时&#xff0c;不过也有很多好的框架&#xff0c;功能也强大&#xff0c;比如我们今天说的…

ISO 11519-2 开环低速 CAN 网络(10K~125Kbps)

ISO 11519-2 标准的物理框图如下图 可理解为一个低速开环 CAN 总线网络&#xff1b;CAN 开环总线网络允许总线最大长度为 1km;最高速度为 125Kbps;这里的两根线是独立的&#xff0c;每根线上串联一个 2.2kΩ 的电阻&#xff1b;节点就是不同的设备&#xff0c;连接到一个开环总…

Mac上使用phpstudy+vscode配置PHP开发环境

使用的工具&#xff1a; 1、系统版本 2、vs code code 3、phpstudy_pro 一、下载vs code code以及必要的插件 1、vs code下载 点击vs code官网下载 选择对应的版本&#xff0c;一般电脑会自动识别对应的版本&#xff0c;点击下载&#xff0c;然后傻瓜式安装&#xff01; 2…

万字长文 详细讲述 计算机网络层

文章目录 网络层网络层的几个重要概念网络层的两个层面 网际协议 IP虚拟互连网络IP 地址IP 地址及其表示方法IP 地址与 MAC 地址地址解析协议 ARPIP 数据报的格式 IP层转发分组过程基于终点的转发最长前缀匹配 网际控制报文协议 ICMPICMP 报文的种类ICMP 的应用举例IPv6 的基本…

Leetcode 494 目标和

题意理解&#xff1a; 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…

js更新地址栏,但是不刷新页面

记录一下第一次遇到更新地址栏但是不刷新页面的需求 有时候会遇到一些需求&#xff0c;比如复制地址&#xff0c;分享给别人 希望也保留筛选条件&#xff0c;但是之前做的时候筛选条件存储到的状态管理工具里面了&#xff0c;地址栏没有&#xff0c;所以为了更快的实现效果&am…

如何下载“ubuntu”在win10系统?

一、下载 企业开源和 Linux |Ubuntu的

解决jenkins需要jdk11,项目需要jdk8的问题

思路&#xff1a;jdk8 采用解压缩模式&#xff0c;jdk11采用安装模式&#xff0c;然后在jenkins中指定jdk路径 下载解压缩jdk8 https://www.oracle.com/java/technologies/downloads/#java8 解压缩&#xff1a;jdk-8u391-linux-i586.tar.gz /lib/ld-linux.so.2: bad ELF inte…

Realm Management Extension领域管理扩展之颗粒保护检查

本节描述了RME引入的颗粒保护检查。颗粒保护检查使得能够在不同的物理地址空间之间动态分配内存区域。 本节将向您介绍以下功能: 颗粒保护表的结构用于颗粒保护检查的故障报告区域在物理地址空间之间的过渡正如在物理地址一节中所述,RME提供了四个物理地址空间。以下图表显示…

搭建 MyBatis 环境

目录 1.添加依赖 2.数据库连接配置 3.配置XML路径 4.下载插件MyBatisX 5.如何使用 6.示例 1.添加依赖 创建新项目时添加两个依赖: MyBatis Framewrok 和 MySQL Driver 。 如果是在已经创建好的项目中配置mybatis环境。需要先下载一个插件&#xff1a;EditStarters 。 然…

JavaScript复习小案例

JavaScript实现简易留言板 效果图 完整代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>留言板</title><style>body {background-color: #f4f4f4;}/* 外部容器样式设置 */.wrapper {width: 400px;heigh…

探索 TCP 与 UDP:网络通信的两门学派(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Vue3组件库 -- element plus 树形选择器组件怎样显示已有的树形菜单?

<el-tree-selectv-model"form.topmneu":data"tableData":props"{ label: title, value: id }":render-after-expand"false"style"width: 100%"check-strictly/> 添加 :props "{ lable : 字段名 , value: 字段…

Java并发之同步二:Java并发工具类

一、CountDownLatch &#xff08;1等多汇总、多等1 开关&#xff09; countdownlatch 底层原理&#xff0c;定义锁资源&#xff1a;0&#xff0c;当资源为0才叫拿到锁&#xff0c;所以countdownlatch也叫做倒数器&#xff0c;拿锁的时候判断是不是0&#xff0c;不是就park&…

NumPy 数据操作实用指南:从基础到高效(下)

文章接上篇&#xff1a; In [53]: from PIL import Image In [60]: dog Image.open(./dog.jpg) dog . . . In [61]: dog_datanp.array(dog) # 图片数据是ndarray # 彩色照片三维&#xff1a;高度&#xff0c;宽度&#xff0c;像素&#xff08;表示不同颜色&#xff09;&…

雪花代码-html版

雪花代码 动画效果 代码 <!DOCTYPE html><html><head><style>body {background-color: #000000;}.snowflake {position: absolute;font-size: 10px;color: #FFFFFF;text-shadow: 1px 1px 1px #000000;user-select: none;}</style></head>…

008-关于FPGA/ZYNQ直接处理图像传感器数据输出的若干笔记(裸板采集思路)

文章目录 前言一、图像传感器厂商二、图像传感器的参数解析三、图像传感器中的全局曝光和卷帘曝光四、处理传感器图像数据流程1.研究当前图像传感器输出格式2.FPGA处理图像数据 总结 前言 最近也是未来需要考虑做的一件事情是&#xff0c;如何通过FPGA/ZYNQ去做显微镜图像观测…

大数据Doris(五十四):SQL函数之日期函数(二)

文章目录 SQL函数之日期函数(二) 一、DAYOFMONTH(DATETIME date) 二、dayofweek(DATETIME date)

基于ssm社区老年人关怀服务系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本社区老年人关怀服务系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数…

题目:七段码(蓝桥OJ 595)

问题描述&#xff1a; 解题思路&#xff1a; 枚举每一种可能组合&#xff08;可以使用二进制数表示&#xff0c;每一个二进制就是一种组合&#xff09;&#xff0c;在判断是否符合题目要求的每一个发光灯管相邻&#xff08;使用并查集方法确定&#xff0c;当每一个发光…