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

 

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