目錄
遞歸查詢實現·start with...connect by prior
遞歸查詢·需求分析
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