CUBRID学习笔记 42 Hierarchical QuerySQL层级查询

cubrid的中sql查询语法Hierarchical QuerySQL层级查询

------ 官方文档是英文的,看不明白可以参看ocracle的同类函数说明.很多都是一样的.

ORACLE中CONNECT BY...START WITH
和ocracle的差不多 ,下面的说明就直接抄袭过来
http://www.iwwenbo.com/oracle-start-with-connect-by/

其中,[where 条件1] 可以不需要,[where 条件1]是对根据[start with 条件2 connect by 条件3]选择出来的记录进行过滤,是针对单条记录的过滤,不会考虑树形查询的树结构;
[start with 条件2 ]限定作为搜索起始点的条件,表示从满足什么条件的记录开始查询;[connect by 条件3]表示查询的连接条件,树形菜单的查询常常是因为记录与记录之间存在某种关系,这种关系通常就作为连接条件。

补充:connect by子句,通常跟关键字“PRIOR”一起使用,“PRIOR”关键字的位置通常有两种,”CONNECT BY PRIOR ID = PID”和”CONNECT BY ID = PRIOR PID”,关键字位置的不同,相应的会导致查询结果的不同。

示例:
--创建表
create table tb_menu(
id number(10) not null,--主键ID
pid number(10) not null,--父菜单ID
title varchar2(50),--菜单名称
);
--添加数据

--父菜单
insert into tb_menu(id, pid,title ) values(1,父菜单1',0);
insert into tb_menu(id, pid,title ) values(2,父菜单2',0);
insert into tb_menu(id, pid,title ) values(3,父菜单3',0);
insert into tb_menu(id, pid,title ) values(4,父菜单4',0);
insert into tb_menu(id, pid,title ) values(5,父菜单5',0);
--一级菜单
insert into tb_menu(id, pid,title) values(6,1,'一级菜单6');
insert into tb_menu(id, pid,title) values(7,1,'一级菜单7');
insert into tb_menu(id, pid,title) values(8,1,'一级菜单8');
insert into tb_menu(id, pid,title) values(9,2,'一级菜单9');
insert into tb_menu(id, pid,title) values(10, 2, '一级菜单10');
insert into tb_menu(id, pid,title) values(11, 2, '一级菜单11');
insert into tb_menu(id, pid,title) values(12, 3,'一级菜单12');
insert into tb_menu(id, pid,title) values(13, 3,'一级菜单13');
insert into tb_menu(id, pid,title) values(14, 3,'一级菜单14');
insert into tb_menu(id, pid,title) values(15, 4,'一级菜单15');
insert into tb_menu(id, pid,title) values(16, 4,'一级菜单16');
insert into tb_menu(id, pid,title) values(17, 4,'一级菜单17');
insert into tb_menu(id, pid,title) values(18, 5,'一级菜单18');
insert into tb_menu(id, pid,title) values(19, 5,'一级菜单19');
insert into tb_menu(id, pid,title) values(20, 5,'一级菜单20');
--二级菜单
insert into tb_menu(id, title, pid) values(21, '二级菜单21',6);
insert into tb_menu(id, title, pid) values(22, '二级菜单22',6);
insert into tb_menu(id, title, pid) values(23, '二级菜单23',7);
insert into tb_menu(id, title, pid) values(24, '二级菜单24',7);
insert into tb_menu(id, title, pid) values(25, '二级菜单25',8);
insert into tb_menu(id, title, pid) values(26, '二级菜单26',9);
insert into tb_menu(id, title, pid) values(27, '二级菜单27',10);
insert into tb_menu(id, title, pid) values(28, '二级菜单28',11);
insert into tb_menu(id, title, pid) values(29, '二级菜单29',12);
insert into tb_menu(id, title, pid) values(30, '二级菜单30',13);
insert into tb_menu(id, title, pid) values(31, '二级菜单31',14);
insert into tb_menu(id, title, pid) values(32, '二级菜单32',15);
insert into tb_menu(id, title, pid) values(33, '二级菜单33',16);
insert into tb_menu(id, title, pid) values(34, '二级菜单34',17);
insert into tb_menu(id, title, pid) values(35, '二级菜单35',18);
insert into tb_menu(id, title, pid) values(36, '二级菜单36',19);
insert into tb_menu(id, title, pid) values(37, '二级菜单37',20);
--三级菜单
insert into tb_menu(id, title, pid) values(38, '三级菜单38',21);
insert into tb_menu(id, title, pid) values(39, '三级菜单39',22);
insert into tb_menu(id, title, pid) values(40, '三级菜单40',23);
insert into tb_menu(id, title, pid) values(41, '三级菜单41',24);
insert into tb_menu(id, title, pid) values(42, '三级菜单42',25);
insert into tb_menu(id, title, pid) values(43, '三级菜单43',26);
insert into tb_menu(id, title, pid) values(44, '三级菜单44',27);
insert into tb_menu(id, title, pid) values(45, '三级菜单45',28);
insert into tb_menu(id, title, pid) values(46, '三级菜单46',28);
insert into tb_menu(id, title, pid) values(47, '三级菜单47',29);
insert into tb_menu(id, title, pid) values(48, '三级菜单48',30);
insert into tb_menu(id, title, pid) values(49, '三级菜单49',31);
insert into tb_menu(id, title, pid) values(50, '三级菜单50',31);
commit;

说明:pid字段存储的是当前菜单节点的上级id,如果菜单节点是顶级节点,该菜单没有上级菜单,则pid应为null,然而在表中记录最好不要为null,建议使用0代替,因为null记录会引起全文扫描。

2.语法说明
oracle中递归查询(树形查询)主要依托于:

select * from table_name [where 条件1] start with 条件2 connect by 条件3;
其中,[where 条件1] 可以不需要,[where 条件1]是对根据[start with 条件2 connect by 条件3]选择出来的记录进行过滤,是针对单条记录的过滤,不会考虑树形查询的树结构;
[start with 条件2 ]限定作为搜索起始点的条件,表示从满足什么条件的记录开始查询;[connect by 条件3]表示查询的连接条件,树形菜单的查询常常是因为记录与记录之间存在某种关系,这种关系通常就作为连接条件。

补充:connect by子句,通常跟关键字“PRIOR”一起使用,“PRIOR”关键字的位置通常有两种,”CONNECT BY PRIOR ID = PID”和”CONNECT BY ID = PRIOR PID”,关键字位置的不同,相应的会导致查询结果的不同。

3.递归查询(树形查询)实践
只有上面的文字说明可能还是搞不清楚树形查询,不过动手来操作一遍就清楚了。

我们从最基本的操作,逐步列出常见的树查询操作,所有的查询以家族中的辈分作比方。

1)查询树中的所有顶级父节点(家族中辈分最大的那一代人)。假如这个树是个目录结构,那么第一个操作总是找出所有的顶级节点,然后再逐个根据顶级节点找到其子节点。

select * from tb_menu m where m.pid = 0;
以上查询得到的就是树的所有顶级节点,也就是家族中辈分最大的那一代人。

2)查找一个节点的直属子节点(家族中某个长辈的所有儿子),此时查找的是直属的子类节点,也是用不到树形查询的。

select * from tb_menu m where m.pid=1;
3)查找一个节点的所有直属子节点(家族中某个长辈的所有直系子孙)。

select * from tb_menu m start with m.id=1 connect by m.pid=prior m.id;
4)查找一个节点的直属父节点(家族中的父亲),此时查找的是节点的直属父节点,也是用不到树形查询的

select c.id, c.title, p.id parent_id, p.title parent_title
from tb_menu c, tb_menu p
where c.pid=p.id and c.id=6
5)查找一个节点的所有直属父节点(家族中一个孩子的所有直系长辈祖先,比如父亲,祖父,……)

select * from tb_menu m start with m.id=38 connect by prior m.pid=m.id;
说明:
上面的3)和5),这两条查询都是树形查询,区别在于”prior”关键字的位置不同,所以决定了查询方式的不同,查询结果也不同。当 pid = prior id时,数据库会根据当前的id迭代出pid与该id相同的记录,所以查询的结果是迭代出了所有的子类记录;而prior pid = id时,数据库会跟据当前的pid来迭代出与当前的pid相同的id的记录,所以查询出来的结果就是所有的父类结果。
仔细看一下数据库的查询结果,可以发现,其实3)和5)的查询顺序也是不同的,3)是自上向下检索,5)是自下向上检索。

以下是一系列针对树结构的更深层次的查询,这里的查询不一定是最优的查询方式,或许只是其中的一种实现而已。

6)查询一个节点的兄弟节点(亲兄弟)

--m.parent=m2.parent-->同一个父亲
select * from tb_menu m
where exists (select * from tb_menu m2 where m.pid=m2.pid and m2.id=6)
7)查询与一个节点同级的节点(族兄弟)。 如果在表中设置了级别的字段,那么在做这类查询时会很轻松,同一级别的就是与那个节点同级的,在这里列出不使用该字段时的实现!

with tmp as
(select a., level leaf
from tb_menu a
start with a.pid = 0
connect by a.pid = prior a.id)
select
from tmp where leaf = (select leaf from tmp where id = 50);
这里使用两个技巧,一个是使用了level来标识每个节点在表中的级别,还有就是使用with语法模拟出了一张带有级别的临时表。

8)查询一个节点的父节点的的兄弟节点(伯父与叔父)

with tmp as(
select tb_menu.*, level lev
from tb_menu
start with pid=0
connect by pid = prior id)

select b.
from tmp b,(select

from tmp
where id = 21 and lev = 2) a
where b.lev = 1

union all

select
from tmp
where pid = (select distinct x.id
from tmp x, --祖父
tmp y, --父亲
(select

from tmp
where id = 21 and lev > 2) z --儿子
where y.id = z.pid and x.id = y.pid);
这里查询分成以下几步。
首先,和第7个一样,将全表都使用临时表加上级别;
其次,根据级别来判断有几种类型,以上文中举的例子来说,有三种情况:
(1)当前节点为顶级节点,即查询出来的lev值为1,那么它没有上级节点,不予考虑。
(2)当前节点为2级节点,查询出来的lev值为2,那么就只要保证lev级别为1的就是其上级节点的兄弟节点。
(3)其它情况就是3以及以上级别,那么就要选查询出来其上级的上级节点(祖父),再来判断祖父的下级节点都是属于该节点的上级节点的兄弟节点。
最后,就是使用union将查询出来的结果进行结合起来,形成结果集。

**来看看官方的**

CREATE TABLE tree(ID INT, MgrID INT, Name VARCHAR(32), BirthYear INT);

INSERT INTO tree VALUES (1,NULL,'Kim', 1963);
INSERT INTO tree VALUES (2,NULL,'Moy', 1958);
INSERT INTO tree VALUES (3,1,'Jonas', 1976);
INSERT INTO tree VALUES (4,1,'Smith', 1974);
INSERT INTO tree VALUES (5,2,'Verma', 1973);
INSERT INTO tree VALUES (6,2,'Foster', 1972);
INSERT INTO tree VALUES (7,6,'Brown', 1981);

CREATE TABLE tree2(id int, treeid int, job varchar(32));

INSERT INTO tree2 VALUES(1,1,'Partner');
INSERT INTO tree2 VALUES(2,2,'Partner');
INSERT INTO tree2 VALUES(3,3,'Developer');
INSERT INTO tree2 VALUES(4,4,'Developer');
INSERT INTO tree2 VALUES(5,5,'Sales Exec.');
INSERT INTO tree2 VALUES(6,6,'Sales Exec.');
INSERT INTO tree2 VALUES(7,7,'Assistant');
INSERT INTO tree2 VALUES(8,null,'Secretary');

SELECT t.id,t.name,t2.job,level
FROM tree t INNER JOIN tree2 t2 ON t.id=t2.treeid
START WITH t.mgrid is null
CONNECT BY prior t.id=t.mgrid
ORDER BY t.id;

结果 能看到层次
id name job level
==============================================================
1 'Kim' 'Partner' 1
2 'Moy' 'Partner' 1
3 'Jonas' 'Developer' 2
4 'Smith' 'Developer' 2
5 'Verma' 'Sales Exec.' 2
6 'Foster' 'Sales Exec.' 2
7 'Brown' 'Assistant' 3

Ordering Data with the Hierarchical Query


SELECT id, mgrid, name, birthyear, level

FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER SIBLINGS BY birthyear;

id mgrid name birthyear level

2         NULL  'Moy'                        1958            1
6            2  'Foster'                     1972            2
7            6  'Brown'                      1981            3
5            2  'Verma'                      1973            2
1         NULL  'Kim'                        1963            1
4            1  'Smith'                      1974            2
3            1  'Jonas'                      1976            2

SELECT id, mgrid, name, birthyear, level
FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER SIBLINGS BY birthyear;

level关键字
理解上面的,就理解下面的了

SELECT id, mgrid, name, LEVEL
FROM tree
WHERE LEVEL=2
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER BY id;

还可以加条件
SELECT LEVEL FROM db_root CONNECT BY LEVEL <= 10;

CONNECT_BY_ISLEAF 关键字
connect_by_isleaf是叶节点1 不是0

SELECT id, mgrid, name, CONNECT_BY_ISLEAF
FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER BY id;

CONNECT_BY_ISCYCLE关键字
这个伪列功能是揪出那些“违反伦理道德”的人。
例如发现一个人既是另外一个人的孙子又是他的爸爸,这显然是不合伦理的,需要尽快发现并进行拨乱反正.
CREATE TABLE tree_cycle(ID INT, MgrID INT, Name VARCHAR(32));

INSERT INTO tree_cycle VALUES (1,NULL,'Kim');
INSERT INTO tree_cycle VALUES (2,11,'Moy');
INSERT INTO tree_cycle VALUES (3,1,'Jonas');
INSERT INTO tree_cycle VALUES (4,1,'Smith');
INSERT INTO tree_cycle VALUES (5,3,'Verma');
INSERT INTO tree_cycle VALUES (6,3,'Foster');
INSERT INTO tree_cycle VALUES (7,4,'Brown');
INSERT INTO tree_cycle VALUES (8,4,'Lin');
INSERT INTO tree_cycle VALUES (9,2,'Edwin');
INSERT INTO tree_cycle VALUES (10,9,'Audrey');
INSERT INTO tree_cycle VALUES (11,10,'Stone');

-- Checking a CONNECT_BY_ISCYCLE value
SELECT id, mgrid, name, CONNECT_BY_ISCYCLE
FROM tree_cycle
START WITH name in ('Kim', 'Moy')
CONNECT BY NOCYCLE PRIOR id=mgrid
ORDER BY id;
id mgrid name connect_by_iscycle
==================================================
1 NULL 'Kim' 0
2 11 'Moy' 0
3 1 'Jonas' 0
4 1 'Smith' 0
5 3 'Verma' 0
6 3 'Foster' 0
7 4 'Brown' 0
8 4 'Lin' 0
9 2 'Edwin' 0
10 9 'Audrey' 0
11 10 'Stone' 1

CONNECT_BY_ROOT 关键字

同一个节点下的节点的connect_by_root是一样的

SELECT id, mgrid, name, CONNECT_BY_ROOT id
FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER BY id;

PRIOR关键字

下面的说明来自oracle
运算符PRIOR被放置于等号前后的位置,决定着查询时的检索顺序。
PRIOR被置于CONNECT BY子句中等号的前面时,则强制从根节点到叶节点的顺序检索,即由父节点向子节点方向通过树结构,我们称之为自顶向下的方式。如:
CONNECT BY PRIOR EMPNO=MGR
PIROR运算符被置于CONNECT BY 子句中等号的后面时,则强制从叶节点到根节点的顺序检索,即由子节点向父节点方向通过树结构,我们称之为自底向上的方式。例如:
CONNECT BY EMPNO=PRIOR MGR
在这种方式中也应指定一个开始的节点。

官方例子
SELECT id, mgrid, name, PRIOR id as "prior_id"
FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER BY id;
id mgrid name prior_id
=====================================================
1 NULL 'Kim' NULL
2 NULL 'Moy' NULL
3 1 'Jonas' 1
4 1 'Smith' 1
5 2 'Verma' 2
6 2 'Foster' 2
7 6 'Brown' 6

SYS_CONNECT_BY_PATH
SYS_CONNECT_BY_PATH (column_name, separator_char)
主要用于树查询(层次查询) 以及 多列转行

SELECT id, mgrid, name, SYS_CONNECT_BY_PATH(name,'/') as [hierarchy]

FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER BY id;
id mgrid name hierarchy
==============================================================
1 NULL 'Kim' '/Kim'
2 NULL 'Moy' '/Moy'
3 1 'Jonas' '/Kim/Jonas'
4 1 'Smith' '/Kim/Smith'
5 2 'Verma' '/Moy/Verma'
6 2 'Foster' '/Moy/Foster'
7 6 'Brown' '/Moy/Foster/Brown'

单元练习例子
CREATE TABLE tbl(seq INT, id VARCHAR(10), parent VARCHAR(10));

INSERT INTO tbl VALUES (1, 'a', null);
INSERT INTO tbl VALUES (2, 'b', 'a');
INSERT INTO tbl VALUES (3, 'b', 'c');
INSERT INTO tbl VALUES (4, 'c', 'b');
INSERT INTO tbl VALUES (5, 'c', 'b');

SELECT seq, id, parent, LEVEL,
CONNECT_BY_ISCYCLE AS iscycle,
CAST(SYS_CONNECT_BY_PATH(id,'/') AS VARCHAR(10)) AS idpath
FROM tbl
START WITH PARENT is NULL
CONNECT BY NOCYCLE PARENT = PRIOR id;
seq id parent level iscycle idpath
=============================================================================
1 'a' NULL 1 0 '/a'
2 'b' 'a' 2 0 '/a/b'
4 'c' 'b' 3 0 '/a/b/c'
3 'b' 'c' 4 1 '/a/b/c/b'
5 'c' 'b' 5 1 '/a/b/c/b/c'
5 'c' 'b' 3 0 '/a/b/c'
3 'b' 'c' 4 1 '/a/b/c/b'
4 'c' 'b' 5 1 '/a/b/c/b/c'

  今天之后是明天,明天之后是后天,后天之后是大后天
  
  SELECT TO_CHAR(base_month + lvl -1, 'YYYYMMDD') h_date

FROM (
SELECT LEVEL lvl, base_month
FROM (
SELECT TO_DATE('201303', 'YYYYMM') base_month FROM db_root
)
CONNECT BY LEVEL <= LAST_DAY(base_month) - base_month + 1
);
h_date
======================
'20130301'
'20130302'
'20130303'
'20130304'
'20130305'
'20130306'
'20130307'
'20130308'
'20130309'
'20130310'
'20130311'
'20130312'
'20130313'
'20130314'
'20130315'
'20130316'
'20130317'
'20130318'
'20130319'
'20130320'
'20130321'
'20130322'
'20130323'
'20130324'
'20130325'
'20130326'
'20130327'
'20130328'
'20130329'
'20130330'
'20130331'

你可能感兴趣的:(CUBRID学习笔记 42 Hierarchical QuerySQL层级查询)