Oracle 中 start with 递归查询、case when 条件表达式、rowid 伪列去重

目录

递归查询·需求分析

递归查询·准备数据

递归查询实现·start with...connect by prior

case when 条件表达式

rowid 伪列删除表中重复数据


递归查询·需求分析

1、开发中经常会有这种需求实现:

2、通常前端显示需要的数据格式如下:

var zNodes =[
    { id:1, pId:0, name:"湖南省", open:true},
    { id:11, pId:1, name:"长沙市"},
    { id:111, pId:11, name:"芙蓉区"},
    { id:112, pId:11, name:"天心区"},
    { id:113, pId:11, name:"岳麓区"},
    { id:114, pId:11, name:"开福区"},
    { id:115, pId:11, name:"雨花区"},
    { id:116, pId:11, name:"望城区"},
    { id:12, pId:1, name:"娄底市"},
    { id:121, pId:12, name:"娄星区"},   
    { id:122, pId:12, name:"冷水江市"},
    { id:123, pId:12, name:"涟源市"},
    { id:124, pId:12, name:"双峰县"},
    { id:125, pId:12, name:"新化县"},
    { id:1251, pId:125, name:"白溪镇"},
    { id:1252, pId:125, name:"洋溪镇"},
    { id:1253, pId:125, name:"吉庆镇"},
    { id:1254, pId:125, name:"曹家镇"},
    { id:2, pId:0, name:"广东省", open:true},
    { id:21, pId:2, name:"深圳市"},
    { id:211, pId:21, name:"罗湖区"}, 
    { id:212, pId:21, name:"福田区"},
    { id:213, pId:21, name:"南山区"},
    { id:214, pId:21, name:"宝安区"},
    { id:215, pId:21, name:"坪山区"},
    { id:216, pId:21, name:"龙岗区"}
];

3、本文介绍的重点不是前端如何实现树形菜单的显示,而是后台如何查询数据库这种数据。

递归查询·准备数据

--菜单目录结构表
create table scott_menu(
    id number(10) primary key, --主键id
    title varchar2(50), --菜单名称
    menu_level number(1),--菜单层级,如 1 表示 1 级菜单,2表示2级菜单,以此类推
    parentId number(10) --父菜单 id
);

--添加1级菜单
insert into scott_menu(id, title, menu_level, parentId) values(1, '湖南省',1,0);--顶级菜单没有父菜单,所以 parentId 为 0
insert into scott_menu(id, title, menu_level, parentId) values(2, '广东省',1,0);


--添加2级菜单
insert into scott_menu(id, title, menu_level, parentId) values(3, '长沙市',2,1);
insert into scott_menu(id, title, menu_level, parentId) values(4, '娄底市',2,1);
insert into scott_menu(id, title, menu_level, parentId) values(5, '深圳市',2,2);

--添加3级菜单
insert into scott_menu(id, title, menu_level, parentId) values(6, '芙蓉区',3,3);
insert into scott_menu(id, title, menu_level, parentId) values(7, '天心区',3,3);
insert into scott_menu(id, title, menu_level, parentId) values(8, '岳麓区',3,3);
insert into scott_menu(id, title, menu_level, parentId) values(9, '开福区',3,3);
insert into scott_menu(id, title, menu_level, parentId) values(10, '雨花区',3,3);
insert into scott_menu(id, title, menu_level, parentId) values(11, '望城区',3,3);

insert into scott_menu(id, title, menu_level, parentId) values(12, '娄星区',3,4);   
insert into scott_menu(id, title, menu_level, parentId) values(13, '冷水江市',3,4); 
insert into scott_menu(id, title, menu_level, parentId) values(14, '涟源市',3,4); 
insert into scott_menu(id, title, menu_level, parentId) values(15, '双峰县',3,4);
insert into scott_menu(id, title, menu_level, parentId) values(16, '新化县',3,4);

insert into scott_menu(id, title, menu_level, parentId) values(17, '罗湖区',3,5);      
insert into scott_menu(id, title, menu_level, parentId) values(18, '福田区',3,5);
insert into scott_menu(id, title, menu_level, parentId) values(19, '南山区',3,5);
insert into scott_menu(id, title, menu_level, parentId) values(20, '宝安区',3,5);
insert into scott_menu(id, title, menu_level, parentId) values(21, '坪山区',3,5);
insert into scott_menu(id, title, menu_level, parentId) values(22, '龙岗区',3,5);  

--添加4级菜单 
insert into scott_menu(id, title, menu_level, parentId) values(23, '白溪镇',4,16);  
insert into scott_menu(id, title, menu_level, parentId) values(24, '洋溪镇',4,16);  
insert into scott_menu(id, title, menu_level, parentId) values(25, '吉庆镇',4,16);  
insert into scott_menu(id, title, menu_level, parentId) values(26, '曹家镇',4,16);  

SQL 运行完成后,Oracle 数据库数据如下:

递归查询实现·start with...connect by prior

1、connect by 是结构化查询中用到的,其基本语法是:

select … from tablename where 条件1 start with 条件2 connect by 条件3 order by 列 ;

条件1 是过滤条件,用于对返回的所有记录进行过滤筛选
条件2 是根结点/起始节点的限定语句
条件3 是连接条件,用于递归迭代的关联。使用 prior 表示上一条记录,比如 "CONNECT BY PRIOR id = parentId",表示上/前一条记录的 id 是后一条记录的 parentId

2、下面循序渐进式的进行编写 SQL:

--查找树中的所有顶级父节点(1级菜单)
select * from scott_menu sm where sm.parentId = 0;
select * from scott_menu sm where sm.menu_level= 1;

--查找某个节点(如id为1)的直属子节点(所有儿子,不包括孙子)
select * from scott_menu sm where sm.parentId=1;

--查找某个节点下的所有子节点(包括所有子孙后代)。如下所示查询 id=1 的菜单下的所有子孙节点
select * from scott_menu sm start with sm.id=1 connect by  prior sm.id = sm.parentId;--前一条记录的 id 是后一条记录的 parentId

--查找所有1级菜单下的全部子孙节点,即查询整颗树,这也是实际中最常见的操作,有了上面的基础,现在则轻而易举了。
--将起始节点设置为所有的一级菜单即可。这查询出来的数据完全符合前端页面的格式,只需要后台再封装成 json 返回给页面即可
select * from scott_menu sm start with sm.parentId=0 connect by  prior sm.id = sm.parentId;
select * from scott_menu sm start with sm.menu_level=1 connect by  prior sm.id = sm.parentId;

--如果需要对查询的结果进行过滤,则使用 where 条件进行筛选,并以 id 倒叙
select * from scott_menu sm where sm.title like '%区%' start with sm.parentId = 0 connect by  prior sm.id = sm.parentId order by id desc;

--需求:查询 "白溪镇" 以及所在的上级 市、省份
select * from scott_menu sm start with sm.title = '白溪镇' connect by sm.id = prior sm.parentId;
--起始第一条数据为 '白溪镇',如何会递归查询下一条的 parentId 等于自己 id 的记录,以此类推

--查询某个节点(如 id= 16)的兄弟节点(亲兄弟,有同一个父节点)
select * from scott_menu sm where exists (select * from scott_menu sm2 where sm.parentId=sm2.parentId and sm2.id=16);

从上面查询整颗树的结果可知,只需要在后台封装好前端所需要的 json 格式的数据返回,前端即可显示。

case when 条件表达式

1、条件表达式格式(Oracle 与 Mysql 通用的写法)(注意:返回值的数据类型必须一致,即不能返回值1是 number,而返回值2确实 char):

语法 1:

     CASE 字段
     WHEN 条件1 THEN 返回值1
     WHEN 条件2 THEN 返回值2
     ...
     ELSE 返回的默认值                 --没有提供默认值,且上面条件都没有匹配时,返回 null

语法 2:

     CASE
     WHEN 条件 1 THEN 返回值1
     WHEN 条件2 THEN 返回值2
     ...
     ELSE 返回的默认值
     END

--方式一。最简单的用法
select t.stuid,t.stuname,case t.gender when '男' then 1 when '女' then 0 else -1 end from student t;
--两者结果完全一样
--方式二,使用条件查询,此时 case 后面不要再加 字段
select t.stuid,t.stuname,case when t.gender = '男' then 1 when t.gender = '女' then 0 else -1 end from student t;

--如果性别为 "男" 则返回 1,否则返回本来的值。注意返回的数据类型必须一致
select t.stuid,t.stuname,case when t.gender = '男' then '1' else t.gender end from student t;

2、Oracle 独有的函数写法:decode(字段,if1,then1,if2,then2,....)

select * from emp;--查询所有
--将姓名 "SMITH" 改为 "张无忌","ALLEN" 改为 "郭靖","WARD" 改为 "李白",其余的默认为无名
--case when then 写法是 Oracle 、Mysql 通用的写法
select case ename 
       when 'SMITH' then '张无忌'
       when 'ALLEN' then '郭靖'
       when 'WARD' then '李白'
       else '无名' end
       from emp;
--将姓名 "SMITH" 改为 "张无忌","ALLEN" 改为 "郭靖","WARD" 改为 "李白",其余的默认为无名
select decode(ename,'SMITH','张无忌','ALLEN','郭靖','WARD','李白','无名') from emp;

rowid 伪列删除表中重复数据

1、oracle 数据库的伪列 rowid 表示该条数据在 oracle 数据库中的物理存储位置,值为长度18的字符串(如 AAATRXAAGAAAK1XAAA)。oracle 内部通常就是使用它来访问数据的。

2、rowid 伪列默认不显示,像 rownum 一样需要显示指定,如 select rowid,t.* from student t ;

3、和 rownum 行号不同的是,rowid 不但可以作为 select 的 where 条件,还可以作为 update、delete 等操作的 where 条件,如:delete from student t where t.rowid = 'AAATRXAAGAAAK1XAAA';

4、所以生产中有一个常见的操作就是用 rowid 来删除表中完全重复的数据,下面先准备测试数据:

--创建学生表
create table STUDENT (
  stuid      VARCHAR2(16) not null,
  stuname    VARCHAR2(16) not null,
  gender     VARCHAR2(2) not null,
  age        NUMBER(8) not null,
  stuaddress VARCHAR2(50),
  enrolldate DATE
);

--插入数据
insert into student values('1','张三丰','男',108,'武当派开山祖师',to_date('2019-08-25 09:25:33','yyyy-mm-dd hh24:mi:ss'));
insert into student values('1','张三丰','男',108,'武当派开山祖师',to_date('2019-08-25 09:25:33','yyyy-mm-dd hh24:mi:ss'));
insert into student values('2','郭襄','女',56,'峨嵋派开山祖师',to_date('2015-06-25 15:00:33','yyyy-mm-dd hh24:mi:ss'));
insert into student values('2','郭襄','女',56,'峨嵋派开山祖师',to_date('2015-06-25 15:00:33','yyyy-mm-dd hh24:mi:ss'));
insert into student values('3','杨不悔','女',27,'明教右使千金',to_date('2020-09-21 11:45:20','yyyy-mm-dd hh24:mi:ss'));

删除表中重复数据行方式一:rowid

--根据单个字段(stuid)进行分组,然后删除重复数据,min(rowid) 取其中 rowid 最新的上,同理可以 max(rowid)
delete from student where 
stuid in ( select stuid from student group by stuid having count(*) > 1) 
and 
rowid not in (select min(rowid) from student group by stuid having count(*) > 1);

--根据多个字段进行分组,然后删除重复数据,min(rowid) 取其中 rowid 最新的上,同理可以 max(rowid)
delete from student where 
(stuid,stuname) in ( select stuid,stuname from student group by stuid,stuname having count(*) > 1) 
and 
rowid not in (select min(rowid) from student group by stuid,stuname having count(*) > 1);

删除表中重复数据行方式二:先取后删再插

--先使用 distnct 关键字进行去重查询,去除结果集中重复的数据行。如果需要对整个表进行去重,则省略 where 条件即可!
select distinct * from student t where t.stuid < 3 order by stuid;
--新建临时表(student_temp),并将去重结果存入进去
--使用 order by 关键字的目的是让后面从临时表再重新插回目标表的时候,数据仍然保持和原来一样的顺序.
create table student_temp as select distinct * from student t where t.stuid < 3 order by stuid;
delete from student t where t.stuid < 3;--然后删除目标表(student)中的所有重复数据
insert into student select * from student_temp;--最后将临时表(student_temp)的数据再插入到目标表(student)中.
drop table student_temp;--删除临时表 student_temp

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章