Oracle入門學習詳解

個人博客原文鏈接

斷斷續續花了半個月多月的時間才較爲詳細的過了一遍Oracle的知識點,以下爲學習過程中我所記錄的知識點和練習的例題。Oracle整個學下來,我感覺相比較SQLServer和MYSQL根本不是同一層次的東西,在Oracle中有許多概念是其它數據庫所沒有的,特別是PLSQL這個設計讓我特別驚訝,沒想到還可以在sql語言中處理數據流程,就像在寫Java代碼一樣,大大提高了開發效率。

環境:Windows 7 64位,Oracle 11g,SQLPlus

注意事項

1.oracle沒有Scott用戶的問題

  • a.首先連接管理員賬戶conn sys/pwd as sysdba;

  • b.執行scott.sql文件
    找到scott.sql文件放在D盤根目錄
    @D:\scott.sql

  • c.連接scott conn scott/TIGER;

  • d.查看當前用戶 show user;

  • e.若要修改scott用戶密碼先登錄管理員賬戶

  • g.更改scott用戶密碼 alter user scott identified by lousen;

2.SQL中的null值

  • 包含null的表達式都爲null
    解決辦法:a不爲null就返回a,a爲null則返回b NVL(a,b)

  • null永遠!=null (不能寫=null)
    解決辦法:select * from emp where comm is null

  • 空值是無效的,未指定的,未知的或不可預計的值,空值不是0或者空格

3.日期和字符串都用單引號表示''

4.SQL語句和SQLPlus命令的區別
SQL:一種語言,ANSI標準,關鍵字不能縮寫,使用語句控制數據庫中的表的定義信息和表中的數據。
SQLPlus:一種環境,Oracle的特性之一,關鍵字可以縮寫,命令不能改變數據庫中的數據的值集中運行。

5.Oracle字符大小寫敏感,日期格式敏感
Oracle嚴格區分字符串的大小寫
默認日期格式DD-MON-RR(日-月-年)
查看默認日期格式
select * from v$nls_parameters;

6.更改日期格式

  • a.更改當前會話的日期格式
    alter session set NLS_DATE_FORMAT='yyyy-mm-dd';

  • b.更改所有數據庫的日期格式(需sys權限)
    alter system set NLS_DATE_FORMAT='yyyy-mm-dd';

7.where解析的順序
從右到左,故在使用例如or的邏輯表達式時可以優化,正確的寫在右邊就可以不用執行左邊的了,提高效率

基本操作

1.查看當前用戶的表
select * from tab;

2.查看指定表結構
desc emp;

3.設置行寬
先查看當前行寬
show linesize
修改行寬
set linesize 200

4.設置列寬(format=for)
設置ename列的寬爲8個字符
col ename format a8
設置sal列的寬爲4個數字的長度
col sal for 9999

5.設置頁面分頁顯示的行數
set pagesize 20

6.修改上一句sql語句

  • a.進入上一句sql語句修改 ed

  • b.執行上一句sql語句 /

7.清屏
host cls

8.錄屏
把操作記錄在d盤根目錄下的筆記.txt中
開始:spool d:\筆記.txt
結束:spool off

9.SQL執行時間的開關
- set timing on
- set timing off

10.執行外部sql文件
例:執行d盤根目錄下的test.sql語句
@d:\test.sql

11.查看執行計劃
explain plan for select * from ....
select * from table(dbms_xplan.display);
從右往左,從下往上看執行計劃

12.登錄
普通用戶:conn username/password[@ip:1521/orcl];
超級管理員:conn username/password[@ip:1521/orcl] as sysdba;

13.查看當前連接數據庫的用戶
show user;

14.查看當前用戶下的表
select * from tab;
注:超級管理員的權限很大,可以使用select * from [用戶名].[表名] 來查看某用戶下的表的數據

15.打開輸出開關
set serveroutput on;

Oracle的體系結構

  • 數據庫
    Oracle數據庫是數據的物理存儲。這就包括(數據文件ORA或者DBF、控制文件、聯機日誌、參數文件)。其實Oracle數據庫的概念和其它數據庫不一樣,這裏的數據庫是一個操作系統只有一個庫。可以看作是Oracle就只有一個大數據庫。

  • 實例
    一個Oracle實例(Oracle Instance)有一系列的後臺進程(Backguound Processes)和內存結構(Memory Structures)組成。一個數據庫可以有n個實例。

  • 用戶
    用戶是在實例下建立的。不同實例可以建相同名字的用戶。

  • 表空間
    表空間是Oracle對物理數據庫上相關數據文件(ORA或者DBF文件)的邏輯映射。一個數據庫在邏輯上被劃分成一到若干個表空間,每個表空間包含了在邏輯上相關聯的一組結構。每個數據庫至少有一個表空間(稱之爲system表空間)。

    每個表空間由同一磁盤上的一個或多個文件組成,這些文件叫數據文件(datafile)。一個數據文件只能屬於一個表空間。

  • 數據文件
    數據文件是數據庫的物理存儲單位。數據庫的數據是存儲在表空間中的,真正是在某一個或者多個數據文件中。而一個表空間可以由一個或多個數據文件組成,一個數據文件只能屬於一個表空間。一旦數據文件被加入到某個表空間後,就不能刪除這個文件,如果要刪除某個數據文件,只能刪除其所屬於的表空間才行。

  • 創建表空間
    一個數據庫下可以建立多個表空間,一個表空間可以建立多個用戶、一個用戶下可以建立多個表。

    基本語法

    create tablespace test  --表空間名稱
    datafile 'd:\Oracle\Space\test.dbf' --表空間對應的數據文件
    size 50m    --定義的表空間初始大小
    autoextend on   --自動增長,當表空間存儲佔滿時,自動增長
    next 10m;   --指定一次增長的大小
    

用戶

數據庫與其它數據庫產品的區別在於,表和其它的數據庫對象都是存儲在用戶下的。

  1. 創建用戶
    例:創建一個賬號爲tom,密碼爲123,默認表空間爲test的用戶

    create user tom 
    identified by 123
    default tablespace test
  2. 用戶賦權
    新創建的用戶沒有任何權限,登錄後會有提示
    Oracle中已存在三個重要的角色:connect角色,resource角色,dba角色。

    • connect角色:是授予最終用戶的典型權利,最基本的
    • resource角色:是授予開發人員的
    • dba角色:擁有全部特權,是系統最高權限

    例:授予tom角色resource權限
    grant resource to tom;

    注:新用戶必須在sys用戶下給其賦予權限,否則無法正常登錄

基本查詢操作

1.給列起別名有三種方式

  • a.select name as "姓名" from user;

  • b.select name "姓名" from user;(推薦使用)

  • c.select name 姓名 from user;

2.查詢表中所有數據

  • a.select * from user;(需要解析*

  • b.select name,age,sex,password from user;(sql優化推薦使用)

3.去掉查詢結果中重複的數據(distnict作用於後面的所有列)
select distinct job,dept from emp;

4.連接符||
select 'Hello' || ' World' "字符串" from dual;
ps:dual表即僞表無實際含義,僅爲符合語法(from後必須跟表)

查詢員工信息:xxx的薪水是xxx
select ename||'的薪水是'||sal "信息" from emp;

5.在。。。之間(between and)
從員工表查詢工資在[1000,2000]的數據
select * from emp where sal between 1000 and 2000;`
注:a.含有邊界 b.小值在前,大值在後

6.在。。。中(in)
從員工表查詢部門號是10和20的員工
select * from emp where deptno in(10,20);

從員工表查詢部門號不是10和20的員工
select * from emp where deptno not in (10,20);

注:如果集合中有null,不能使用not in,但可以使用in

7.模糊查詢
查詢名字是4個字的員工
select * from emp where ename like '____'

查詢名字中含有下劃線的員工
select * from emp where ename like '%\_%' escape '\'

8.order by (order by作用於後面的所有列,desc只作用於離它最近的列)

  • a.order by後面可以 + 列、表達式、別名、序號(列的序號)

    查詢員工id,姓名,月薪,年薪並按年薪降序排列(表達式)
    select empno,ename,sal,sal*12 from emp order by sal*12 desc

    查詢員工id,姓名,月薪,年薪並按年薪降序排列(別名)
    select empno,ename,sal,sal*12 "年薪" from emp order by "年薪" desc

    查詢員工id,姓名,月薪,年薪並按年薪降序排列(序號)
    select empno,ename,sal,sal*12 "年薪" from emp order by 4 desc

  • b.多個列排序
    從員工表查詢數據,先根據員工編號升序排列,編號相同的話就根據月薪升序排列
    select * from emp order by deptno,sal;

    從員工表查詢數據,先根據員工編號升序排列,編號相同的話就根據月薪降序排列
    select * from emp order by deptno,sal desc;

    從員工表查詢數據,先根據員工編號降序排列,編號相同的話就根據月薪降序排列
    select * from emp order by deptno desc,sal desc;

  • c.null的排序 (注:在排序時null值最大)
    查詢員工的信息,按照獎金升序排序,null在下邊
    select * from emp order by comm;

    查詢員工的信息,按照獎金降序排序,null在上邊
    select * from emp order by comm desc;

    如何實現降序排列的null值在上邊呢?
    select * from emp order by comm desc nulls last;

單行函數

一.字符函數

1.大小寫控制函數

  • a.lower(轉小寫)

  • b.upper(轉大寫)

  • c.initcap(首字母大寫)
    select lower('Hello WOrld') 轉小寫,upper('Hello WOrld') 轉大寫,initcap('hello wOrld') 首字母大寫 from dual;

2.字符控制函數

  • a.concat(連接字符串)
    select concat('hello',' world') from dual;

  • b.substr(截取字符串)
    select substr('Hello World',1,5) from dual;

  • c.length(字符數),legthb(字節數)
    select length('Hello World') 字符數,lengthb('Hello World') 字節數 from dual;
    select length('上海') 字符數,lengthb('上海') 字節數 from dual;

  • d.instr(查詢自子符串的位置,從1開始數)
    select instr('hello world','ll') 位置 from dual;

  • e.lpad(左填充) rpad(右填充)
    select lpad('abcd',10,'*') 左,rpad('abcd',10,'*') 右 from dual;

  • f.trim(去掉前後指定的字符)
    select trim('H' from 'Hello WorldH') from dual;

  • g.replace(替換指定的字符)
    select replace('Hello World','l','*') from dual;

二.數字函數

  • round(四捨五入)
    select round(45.926,2) 一,round(45.926,1) 二,round(45.926,0) 三,round(45.926,-1) 四,round(45.926,-2) 五 from dual;

  • trunc(截斷)
    select trunc(45.926,2) 一,trunc(45.926,1) 二,trunc(45.926,0) 三,trunc(45.926,-1) 四,trunc(45.926,-2) 五 from dual;

  • mod(求餘)
    select mod(1600,300) 餘數 from dual;

三.日期函數
Oracle中的日期型數據實際含有兩個值:日期和時間。
默認日期格式爲:DD-MON-RR

  1. 當前日期
    select sysdate from dual;

  2. 格式化時間
    select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual;

  3. 日期的數學運算
    a.在日期上加上或減去一個數字(天數)結果還是日期
    b.兩個日期相減返回日期之差的天數
    c.可以用數字除24向日期中加上或減去小時

    select ename,hiredate,(sysdate-hiredate) 天,(sysdate-hiredate)/7 星期,(sysdate-hiredate)/30 月,(sysdate-hiredate)/365 年 from emp;

    注:不允許日期+日期

  4. months_between(相差的月數)
    select ename,hiredate,(sysdate-hiredate)/30 一,months_between(sysdate,hiredate) 二 from emp;

  5. add_months(向指定日期添加指定月數)
    select add_months(sysdate,53) from dual;

  6. last_day(本月的最後一天)
    select last_day(sysdate) from dual;

  7. next_day(指定日期的下一個星期,’1’表示下個星期日,或直接用’sunday’表示)
    select next_day(sysdate,'1') from dual;

  8. 在日期中插入字符串
    select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss"今天是"day') from dual;

  9. round(日期的四捨五入)
    同數字

  10. trunc(日期的截取)
    同數字

四.數據類型轉換
1. 隱式
varchar2 or char -> number
varchar2 or char -> date
number -> varchar2
date -> varchar2

  1. 顯式

    • to_char(日期格式轉換)
      to_char(date,'yyyy-mm-dd hh24:mi:ss')
      注:’YYYY’=年,’MM’=月,’DD’=日,’DAY’=星期

    • to_char(數字格式轉換)
      to_char(number,'L9,999.99')
      注:’9’=數字,’0’=零,’$’=美元,’L’=本地貨幣符號,’.’=小數點,’,’=千位符

    • to_number(字符轉數字)
      to_number('$123.33','$999.99')

    • to_date(字符轉日期)
      to_date('2018-05-26 12:38:49','yyyy-mm-dd hh24:mi:ss')

五.通用函數(適用於任何數據類型,包括null)
1. nvl (a,b)
當a爲null時,返回b,否則返回a

  1. nvl(a,b,c)
    當a爲null時,返回c,否則返回b

  2. nullif(a,b)
    當a=b時,返回null,否則返回a

  3. coalesce(a,b,c…)
    從左到右找到第一個不爲null的值並返回

六.條件表達式
1. case表達式
實現董事長工資+1000,經理+800,其他+400
select empno,ename,job,sal 漲前,case job when 'PRESIDENT' then sal+1000 when 'MANAGER' then sal+800 else sal+400 end 漲後 from emp;

  1. decode表達式(Oracle獨有)
    實現董事長工資+1000,經理+800,其他+400
    select empno,ename,job,sal 漲前,decode(job,'PRESIDENT',sal+1000,'MANAGER',sal+800,sal+400) 漲後 from emp;

多行函數(分組函數)

一.什麼是分組函數
分組函數作用於一組數據,並對一組數據返回一個值。

二.常見的組函數
1. AVG
計算所有員工的平均工資
select avg(sal) "平均工資" from emp;

  1. COUNT
    計算所有員工的數量
    select count(*) "總數" from emp;

  2. MAX
    查找最高工資
    select max(sal) "最高工資" from emp;

  3. MIN
    查找最低工資
    select min(sal) "最低工資" from emp;

  4. SUM
    計算工資之和
    select sum(sal) "總和" from emp;

三.組函數與空值
組函數忽略空值。

例:計算平均值時,如果存在空值,不同的方式得到的平均值結果也不同
1. 用總獎金數除以總人數(總人數不會有null,但有人的獎金爲null)
select sum(comm)/count(*) from emp;

  1. 用總獎金數除以總獲得獎金的人數(並不會把null加入運算)
    select sum(comm)/count(comm) from emp;

  2. 直接用avg組函數(並不會把null加入運算)
    select avg(comm) from emp;

故:需根據具體情況來看使用哪種平均數。以上三種情況1的值和2,3的不同,2和3的值相同。

在組函數中使用NVL函數,使分組函數無法忽略空值。
select avg(nvl(comm,0)) from emp;

四.多個列的分組(GROUP BY)
使用group by子句將表中的數據分成若干組。
1. group by子句(單列)
注1:在select列表中所有未包含在組函數中的列,都應該包含在group by子句中 。
例:按部門計算每個部門的平均工資
select deptno,avg(sal) from emp group by deptno;

注2:然而,包含在group by子句中的列不必包含在select列表中。
    `select avg(sal) from emp group by deptno;`
  1. group by子句(多列)
    先按第一個列分組,第一個列相同在按第二列分組,以此類推。
    select deptno,job,sum(sal) from emp group by deptno,job order by 1;

五.過濾分組(HAVING)
使用having過濾分組的前提條件。
1. 行已經被分組了
2. 使用了組函數
3. 滿足having子句中條件的分組將被顯示
例:按部門查找每個部門的最高工資並顯示大於3000的數據
select deptno,max(sal) from emp group by deptno having max(sal)>3000;

where和having的區別:
where後面不可以使用多行函數。
select deptno,avg(sal) from emp where deptno=10 group by deptno;
等價於
select deptno,avg(sal) from emp group by deptno having deptno=10;

注:sql優化原則,儘量使用where,先過濾再分組,降低負荷。

六.group by子句的增強
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
等價於
select deptno,job,sum(sal) from emp group by deptno,job;
+
select deptno,sum(sal) from emp group by deptno;
+
select sum(sal) from emp;

可按照報表的格式輸出數據。

例:根據部門和職位計算工資總和的報表
1. 取消deptno的重複列顯示,每行空兩格。
break on deptno skip 2
2. 執行group by子句語句的增強
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
3. 取消報表格式
break on null;

多表查詢

一.笛卡爾集
1. 產生條件
省略連接條件
連接條件無效
所有表中的所有行互相連接

  1. 爲避免笛卡爾集,可以在where中加入有效的連接條件
    例:有n張表,則需要至少(n-1)個有效的連接條件

  2. 在實際運行環境中,應避免使用笛卡爾全集
    例:有兩張表,則產生的表(笛卡爾全集)的數據爲兩張表的列相加,兩張表的行相乘

二.等值連接
例:從員工表和部門表查詢員工信息:員工號 姓名 月薪 部門名稱
select e.empno,e.ename,e.sal,d.dname from emp e,dept d where e.deptno=d.deptno;

三.不等值連接
例:從員工表和工資級別表查詢工資級別在最低級別和最高級別之間的員工的信息:員工號 姓名 月薪 工資級別
select e.empno,e.ename,e.sal,s.grade from emp e,salgrade s where e.sal between s.losal and s.hisal;

四.外連接
對於某些不成立的記錄,仍然希望包含在最後的結果中。
例:where e.deptno=d.deptno
當該部門不存在員工時,e.deptno爲null,這個結果將不會被顯示,若想要顯示需使用外連接

  1. 左外連接
    當where e.deptno=d.deptno不成立的時候,等號左邊的表仍然被包含在最後的結果中
    寫法:where e.deptno=d.deptno(+)
    select d.deptno 部門號,d.dname 部門名稱,count(e.empno) 人數 from emp e,dept d where e.deptno=d.deptno(+) group by d.deptno,d.dname;

  2. 右外連接
    當where e.deptno=d.deptno不成立的時候,等號右邊的表仍然被包含在最後的結果中
    寫法:where e.deptno(+)=d.deptno
    select d.deptno 部門號,d.dname 部門名稱,count(e.empno) 人數 from emp e,dept d where e.deptno(+)=d.deptno group by d.deptno,d.dname;

五.自連接
通過表的別名,將同一張表視爲多張表
例:從員工表中查詢員工信息: 員工姓名 老闆姓名
select e.ename 員工姓名,b.ename 老闆姓名 from emp e,emp b where e.mgr=b.empno;

注:自連接不適合大表操作

六.層次查詢
代替自連接查詢,只對一張表進行操作,適合大表操作
原理:樹形結構
例:從員工表中查詢員工信息: 員工姓名 老闆姓名
分析:當前員工的mgr即爲上一層(prior)的員工,(mgr is null表示根節點)
select level,empno,ename,mgr from emp connect by prior empno=mgr start with mgr is null;

注:level是僞列

子查詢

在一個查詢的內部還包含另一個查詢,則此查詢稱爲子查詢
子查詢所要解決的問題:不能一步求解

  1. 常用格式
    例:查詢工資比SCOTT高的員工信息
    select * from emp where sal > (select sal from emp where ename='SCOTT');

  2. 需要注意的問題

    • 合理的書寫風格

    • 括號

    • 可以在主查詢的where、select、having、from後面使用子查詢

      • where
        select * from emp where sal > (select sal from emp where ename='SCOTT');

      • select
        select empno,ename,sal,(select job from emp where empno=7839) from emp;

      • having
        select deptno,sum(sal) from emp group by deptno having deptno=(select deptno from emp where ename='SCOTT');

      • from
        select * from (select empno,ename,sal from emp );

    • 強調from後面的子查詢
      select * from (select empno,ename,sal,sal*12 annsal from emp);

    • 主查詢和子查詢可以不是同一張表,只要子查詢返回的結果主查詢可用即可
      select * from emp where deptno=(select deptno from dept where dname='SALES');

    • 一般不在子查詢中排序;但top-n分析問題中,必須對子查詢排序
      top-n問題:查詢工資排名前三的員工的信息
      select rownum,empno,ename,sal from (select * from emp order by sal desc) where rownum<=3;

      注:rownum是僞列,永遠按照默認的順序生成,rownum只能使用<和<=,不能使用>和>=
      注:rownum永遠從1開始,要分頁需要使用特殊的手段
      select * from (select rownum r,e1.* from (select * from emp order by sal) e1 where rownum <=8 ) where r >=5;

    • 一般先執行子查詢,再執行主查詢;但相關子查詢例外
      相關子查詢:將主查詢中的值 作爲參數傳遞給子查詢
      例:查詢工資大於平均工資的員工的信息
      select empno,ename,sal,(select avg(sal) from emp where deptno=e.deptno) avgsal from emp e where sal > (select avg(sal) from emp where deptno=e.deptno);

    • 單行子查詢只能使用單行操作符,多行子查詢只能使用多行操作符

    • 子查詢中的null

  3. SQL優化
    儘量使用多表查詢代替子查詢

  4. 多行子查詢

    • in(在集合中)
      例:查詢部門名稱是SALES和ACCOUNTING的員工
      select * from emp where deptno in (select deptno from dept where dname='SALES' or dname='ACCOUNTING');

    • any(和集合中任意一個值比較)
      例:查詢工資比30號部門任意一個員工高的員工信息
      select * from emp where sal > any (select sal from emp where deptno=30);

    • all(和集合中的所有值比較)
      例:查詢工資比30號部門所有員工高的員工信息
      select * from emp where sal > all (select sal from emp where deptno=30);

  5. 多行子查詢中的null

    • in
      in後面的集合中可以包含null

    • not in
      not in後面的集合不可以包含null
      解決辦法:select * from emp where empno not in (select mgr from emp where mgr is not null);

集合運算

  1. UNION/UNION ALL(並集)

    • UNION
      返回兩個集合的所有記錄,重複部分只計算一次
      select deptno,job,sum(sal) from emp group by deptno,job
      union
      select deptno,to_char(null),sum(sal) from emp group by deptno
      union
      select to_number(null),to_char(null),sum(sal) from emp;

    相當於
    select deptno,job,sum(sal) from emp group by rollup(deptno,job);

    • UNION ALL
      返回兩個集合的所有記錄,重複部分計算兩次
      SQL優化:兩者都可以時,儘量使用union all
  2. INTERSECT(交集)
    返回同時屬於兩個集合的記錄
    例:顯示薪水同時位於700~1300和1201~1400的員工的信息
    select ename,sal from emp where sal between 700 and 1300 intersect select ename,sal from emp where sal between 1201 and 1400;

  3. MINUS(差集)
    返回屬於第一個集合,但不屬於第二個集合的記錄
    例:顯示薪水位於700~1300但不位於1201~1400的員工的信息
    select ename,sal from emp where sal between 700 and 1300 minus select ename,sal from emp where sal between 1201 and 1400;

  4. 集合運算注意事項

    • 參與運算的各個集合必須列數相同 且類型一致
    • 採用第一個集合作爲最後的表頭(故別名應在第一個集合)
    • order by永遠寫在最後(故order by應在最後一個集合)
    • 可使用括號來改變集合執行順序
  5. SQL優化原則
    儘量不要使用集合運算,集合越多效率越低

處理數據

  • SQL的類型

    1. DML(data manipulation language 數據操作語言):insert update delete select
    2. DDL(data definition language 數據定義語言): create table,alter table,drop table,truncate table,create/drop view,sequence,index,synonym(同義詞)
    3. DCL(data control language 數據控制語言):grant(授權) revoke(撤銷權限)
  • DML語句

    1. insert
      基本語法
      insert into emp(empno,ename,sal,deptno) values(1002,'Mary',2000,10);

      一次性插入多條數據
      例:一次性將emp中所有10號部門的員工插入到emp10中
      insert into emp10 select * from emp where deptno=10;
      注:不必書寫values子句
      注:子查詢中的值的列表應與insert子句中的列名對應(*代表結構完全一致)

      注:海量插入數據

      1. 數據泵(PLSQL程序:dbms_datapump)
      2. SQL*Loader工具
      3. 外部表
    2. delete
      基本語法
      delete from emp where deptno=10;

      delete和truncate的區別:

      1. delete逐條刪除;truncate先摧毀表 再重建
      2. (*)delete是DML(可以回滾) truncate是DDL(不可以回滾)
      3. delete不會釋放空間 truncate會
      4. delete可以閃回(flashback) truncate不可以
      5. delete會產生碎片 truncate不會

      注:在ORACLE中delete比truncate速度快
      原因:undo數據(還原數據)

    3. update
      基本語法
      update emp set deptno=20 where deptno=10;

  • 事務
    ORACLE中事務的標誌

    1. 起始標誌
      事務中第一條DML語句

    2. 結束標誌

      • 提交
        顯式:commit
        隱式:正常退出exit,DDL,DCL

      • 回滾
        顯示:rollback
        隱式:非正常退出,斷電,宕機

    回滾到保留點

    • 使用savepoint語句在當前事務中創建保存點
    • 使用roll back to savepoint語句回滾到指定的保存點
      savepoint a;
      insert...
      delete...
      ...
      roll back to savepoint a;

    數據庫的隔離級別
    Oracle 支持的 2 種事務隔離級別:READCOMMITED, SERIALIZABLE。Oracle 默認的事務隔離級別爲: READ COMMITED

創建和管理表

  • 創建表
    基本語法

    create table emp2(
        ename varchar2(10),
        age number
    );

    在創建表的同時插入數據

    create table empinfo
    as
    select e.empno,e.ename,e.sal,e.sal*12 annsal,d.dname
    from emp e, dept d
    where e.deptno=d.deptno;

    創建表時只複製表結構,不插入數據

    create table emp3
    as
    select * from emp
    where 1=2;

    注:where的條件永爲假,故只複製結構,不復制數據

  • 修改表

    1. 追加列
      alter table emp2 add photo blob;

    2. 修改列
      alter table emp2 modify ename varchar2(40);

    3. 刪除列
      alter table emp2 drop column photo;

    4. 重命名列
      alter table emp2 rename column ename to username;

    5. 重命名錶
      rename emp3 to test1;

    6. 刪除表
      drop table test1;

    7. Oracle的回收站

      • 查看回收站
        show recyclebin;

      • 清空回收站
        purge recyclebin;

      • 閃回
        flashback table test1 to before drop;
        若已存在同名的表
        flashback table test1 to before drop rename to test2;

      • 查詢回收站的表
        表名加上”“即可

      • 回收站有兩個同名的表
        根據刪除時間分辨
        閃回優先恢復最近刪除的表

      • 管理員用戶沒有回收站
        使用管理員用戶進行刪除操作時需十分謹慎

  • 約束
    例:創建一個帶約束的表

    create table student
    (
        sid number constraint student_pk primary key,
        sname varchar2(20) constraint student_name_notnull not null,
        gender varchar2(2) constraint student_gender check (gender in ('男','女')),
        email varchar2(40) constraint student_email_unique unique
        constraint student_email_notnull not null,
        deptno number constraint student_fk references dept(deptno) on delete set null
    );

其他數據庫對象

  • 視圖(view)
    視圖的概念

    • 視圖是一種虛表
    • 視圖建立在已有表的基礎上,視圖依賴建立的表稱爲基表
    • 向視圖提供數據內容的語句爲select語句,可以將視圖理解爲存儲起來的select語句
    • 視圖向基表提供表數據的另一種形式

    視圖的優點

    • 限制數據訪問
    • 簡化複雜查詢
    • 提供數據的獨立性
    • 同樣的數據,可以有不同的顯示方式

    注:視圖並不能提高性能
    注:最好不要通過視圖對錶進行修改,通常加上with read only;

    基本語法

    create  or replace  view empinfoview
    as
    select e.empno,e.ename,e.sal,e.sal*12 annsal,d.dname
    from emp e, dept d
    where e.deptno=d.deptno
    with read only;
  • 序列(sequence)

    • 序列的概念

      • 自動提供唯一的數值
      • 共享對象
      • 主要用於提供主鍵值
      • 將序列值裝入內存可以提高訪問效率
        注:最好一個表使用一個序列,不要多個表共用一個,否則會產生裂縫
    • 序列的語法

      create sequence dept_seq
      increment by n 【設置步長,默認爲1】
      start with n   【設置起始數,默認爲1】
      maxvalue n|nomaxvalue   【設置最大值,默認爲nomaxvalue】
      minvalue n|nominvalue   【設置最小值,默認爲nominvalue】
      cycle|nocycle   【設置是否循環,默認爲不循環】
      cache n|nocache 【設置緩存長度,默認爲20】
      

    故,一般情況下只需要使用create sequence dept_seq;即可,其他爲默認值。

    • 如何使用序列
      配合nextval和currval僞列

      • nextval
        返回序列下一個有效值,任何用戶都可以使用

      • currval
        返回序列當前的值

      • nextval必須在currval之前指定,兩者必須同時有效

      一般在插入數據時使用
      insert into test2 values(dept_seq.nextval,'Mike');

    • 序列裂縫

      • 回滾
      • 系統異常
      • 多個表共用同一序列
    • 刪除序列
      刪除之後,序列不可再次被引用
      drop sequence dept_seq

  • 索引(index)
    索引的概念
    - 相當於目錄
    - 一種獨立於表的模式對象,可以存儲在與表不同的磁盤或表空間中
    - 通過指針加快oracle服務器的查詢速度
    - 索引被破壞不會對錶產生影響,影響的只是查詢速度
    - 索引一旦創建,oracle會自動進行維護

    創建索引

    • 自動創建
      定義primary key和unique時,oracle自動創建索引

    • 手動創建
      例:在表test2的name列上創建索引
      create index test2_name_index on test2(name);

    什麼時候創建索引

    • 列中值分佈範圍廣泛
    • 列經常出現在where子句或連接條件中
    • 表經常被訪問且數據量很大,訪問的數據大概佔數據總量的2%到4%

    刪除索引
    drop index test2_name_index;

  • 同義詞
    同義詞概念

    • 使用同義詞訪問相同的對象
    • 方便訪問其他用戶的對象
    • 縮短對象名字的長度
    • 所有的數據對象都可以起別名

    基本語法
    create synonym hremp for hr.employess;

PL/SQL

PLSQL是Oracle對sql語言的過程化擴展,指在SQL命令語言中增加了過程處理語句(如分支、循環等),使SQL語言具有過程處理能力。

運行環境:Windows 7 64位,plsqldevelop 10

  1. PL/SQL程序結構

    declare
        說明部分(變量說明,光標申請,例外聲明)
    begin
        語句序列
    exception
        例外處理語句
    End;
    
  2. 變量和常量說明

    • 說明變量
      var1 char(10); –oracle建表時字段的變量
      married boolean:=true; –賦初值
      psal number(10,2); –保留兩位小數
      my_name emp.ename%type; –引用類型變量,即my_name的類型與emp.ename一致
      emp_rec emp%rowtype; –記錄型變量,代表一行的變量

    • 記錄型變量分量的引用
      emp_rec.ename = ‘Mike’;

    • 定義常量
      married constant boolean:=true;

    注意: := 賦值符號等價於java中的=號;
    注意: = 邏輯等,判斷兩個值是否相等,等價於java中的==號?;

    例:查詢員工號爲7369的員工的姓名,薪水並打印

    declare
        pename emp.ename%type;
        psal emp.sal%type;
    begin
        select ename,sal into pename,psal from emp where empno=7369;
        dbms_output.put_line(pename||'的工資是'||psal);
    end;
  3. if分支

    • 語法1
      IF 條件 THEN
      語句1;
      語句2;
      END IF;

    • 語法2
      IF 條件 THEN
      語句1;
      ELSE
      語句2;
      END IF;

    • 語法3
      IF 條件 THEN
      ELSIF 條件 THEN
      ELSIF 條件 THEN
      ELSIF 條件 THEN

      ELSE 語句
      END IF;

    • 例:判斷不同年齡段的人羣

      accept num prompt ‘請輸入一個年齡’;
      declare
          mynum number := &num;
      begin
          if mynum < 18 then
              dbms_output.put_line('未成年人');
          elsif mynum >= 18 and mynum < 40 then
              dbms_output.put_line('中年人');
          elsif mynum >= 40 then
              dbms_output.put_line('老年人');
          end if;
      end;
      
  4. Loop循環

    • 語法1
      WHILE 條件 LOOP

      END LOOP;

    • 語法2(推薦)
      Loop
      EXIT when 條件;

      End loop;

    • 語法3
      FOR I IN 1 … 3 LOOP
      語句序列 ;
      END LOOP ;

    • 例:輸出1到10的數字

      declare
          mynum number := 1;
      begin
          Loop
          EXIT  when   mynum > 10;
          dbms_output.put_line(mynum);
          mynum := mynum+1;
          End loop;
      end;
      
  5. 遊標(光標)
    遊標相當於java中集合的概念,可以用來存儲查詢返回的多條數據。

    光標的屬性:

    1. %isopen(遊標打開狀態)
    2. %rowcount(影響的行數)
    3. %found(遊標中還有值)
    4. %notfound(遊標中沒有值)

    遊標的使用步驟:

    1. 現在declare中定義遊標:cursor c1 is select job from emp;
    2. 在begin打開遊標:open c1;
    3. 取一行遊標的值到所定義的變量中:fetch c1 into pjob;
    4. 遊標退出循環的條件:exit when c1%notfound
    5. 結束遊標:close c1;

    注:pjob變量必須和emp表中的job列表的類型一致

    例1:使用遊標輸出emp表的員工姓名和工資

    declare
        --定義光標和變量
        cursor cemp is select ename,sal from emp;
        pename emp.ename%type;
        psal emp.sal%type;
    
    begin
        --打開光標
        open cemp;
    
        loop
            --把光標中存儲的值賦給變量
            fetch cemp into pename,psal;
            --當光標中沒有值時退出循環
            exit when cemp%notfound;
            dbms_output.put_line(pename||'的薪水是'||psal);
    
        end loop;
        --關閉光標
        close cemp;
    
    end;

    例2:查詢某部門的員工姓名

    declare
      cursor cemp(dno emp.deptno%type) is select ename from emp where deptno=dno;
      pename emp.ename%type;
    begin
      open cemp(20);
      loop
        fetch cemp into pename;
        exit when cemp%notfound;
        dbms_output.put_line(pename);
      end loop;
      close cemp;
    end;
  6. 異常
    PLSQL的異常處理機制和Java十分相似,都是用來增強程序的健壯性和容錯性的

    系統定義異常

    • no_data_found (沒有找到數據,空值異常)
    • too_many_rows (select …into語句匹配多個行)
    • zero_divide (除零異常)
    • value_error (算術或轉換錯誤)
    • timeout_on_resource (在等待資源時發生超時)

    例:捕獲除零異常

    declare 
        mynum number;
    begin
        mynum := 1/0;
    exception
        when zero_divide then
            dbms_output.put_line('除零異常');
        when value_error then
            dbms_output.put_line('轉換異常');
        when no_data_found then
            dbms_output.put_line('空值異常');
        when others then
            dbms_output.put_line('其他異常');
    end;
    

    自定義異常
    在聲明中定義異常,用raise拋出異常,再捕獲異常

    例:查詢部門編號是50的員工

    declare
        no_found exception;  --自定義異常
        cursor cemp is select ename from emp where deptno=50;
        pename emp.ename%type;
    
    begin
        open cemp;
            fetch cemp into pename;
            if cemp%notfound then
                raise no_found;  --拋出異常
            end if;
        close cemp;
    
        exception --捕獲異常
            when no_found then
                dbms_output.put_line('沒有找到該員工');
            when others then
                dbms_output.put_line('其他異常');
    end;
    
  7. 綜合練習
    例1:打印每個年份(80,81,82,87)入職員工的人數

    詳細設計:
    sql語句
    select to_char(hiredate,’yyyy’) from emp;
    –>遊標–>循環判斷–>退出循環–>打印結果

    變量
    count80 number := 0;
    count81 number := 0;
    count82 number := 0;
    count87 number := 0;

    編碼:

    declare
      --入職年份
      cursor cemp is select to_char(hiredate,'yyyy') from emp;
      phiredate varchar2(4);
      --入職人數
      count80 number := 0;
      count81 number := 0;
      count82 number := 0;
      count87 number := 0;
     begin
        --打開光標
       open cemp;
            --循環判斷,累計疊加
            loop
               --獲取年份
              fetch cemp into phiredate;
              exit when cemp%notfound;
    
              if phiredate = '1980' then
                count80 := count80+1;
               elsif phiredate = '1981' then
                count81 := count81+1;
               elsif phiredate = '1982' then
                count82 := count82+1; 
                else
                  count87 := count87+1;
               end if;
    
            end loop;
        --關閉光標
       close cemp;
       --打印結果
       dbms_output.put_line('總人數:'||(count80+count81+count82+count87));
       dbms_output.put_line('1980:'||count80);
       dbms_output.put_line('1981:'||count81);
       dbms_output.put_line('1982:'||count82);
       dbms_output.put_line('1987:'||count87);
     end;
    

    例2:爲員工漲工資。從最低工資調起每人漲10%,但工資總額不能超過5萬元,請計算漲工資的人數和漲工資後的工資總額,並輸出漲工資人數及工資總額。

    詳細設計:
    sql語句
    select empno,sal from emp order by sal;
    –>遊標–>循環–>退出條件:1.總金額>5w 2.加上最後漲工資的員工總金額>5w 3.not found

    變量:1. 初始值 2.最終得到
    漲工資的人數: countEmp number := 0;
    漲後的工資總額:salTotal number;
    1.漲前總金額:select sum(sal) into salTotal from emp;
    2.漲後=漲前+sal*0.1

    編碼:

    declare 
        cursor cemp is select empno,sal from emp order by sal;
        pempno emp.empno%type;
        psal emp.sal%type;
        --漲工資人數
        countEmp number := 0;
        --漲後總工資
        salTotal number;
    begin
        open cemp;
        --漲後總工資的初始值爲漲前總工資
        select sum(sal) into salTotal from emp;
        loop
            --漲後總工資大於5w時
            exit when salTotal > 50000;
                fetch cemp into pempno,psal;
            --所有人漲完工資了
            exit when cemp%notfound;
            --加上最後漲工資的員工總金額>5w 
            exit when (salTotal + 0.1*psal) > 50000;
                update emp set sal = psal*1.1 where empno=pempno;
                countEmp := countEmp+1;
                salTotal := salTotal + 0.1*psal;
        end loop;
        close cemp;
        dbms_output.put_line('漲工資人數爲:'||countEmp);
        dbms_output.put_line('漲後總工資:'||salTotal);
    end;
    

    例3: 實現按部門分段(6000 以上、(6000 ,3000) 、3000 元以下)統計各工資段的職工人數、以及各部門的工資總額( 工資總額中不包括獎金)。

    詳細設計:
    SQL語句
    外循環
    部門:select deptno from dept;
    內循環
    查部門中的員工薪水: select sal from emp where deptno=dno;

    變量:1. 初始值 2.最終得到
    部門號
    pdep emp.deptno%type;
    工資
    psal emp.sal%type;
    每個段的人數
    count1 number; count2 number; count3 number;
    部門的工資總額
    salTotal number;

    1. select sum(sal) into salTotal from emp where deptno=pdep;
    2. 累加

    編碼:
    先創建一張表msg來存儲數據

    create table msg(
        deptno number,
        count1 number,
        count2 number,
        count3 number,
        salTotal number
    );
    

    用plsql處理數據,將所得數據插入msg表

    declare
        cursor cdept is select deptno from dept;
        --聲明部門號
        pdep dept.deptno%type;
        cursor cemp(dno emp.deptno%type) is select sal from emp where deptno = dno;
        --聲明薪水
        psal emp.sal%type;
        --聲明每個部門每個工資段的人數
        count1 number;
        count2 number;
        count3 number;
        --聲明部門工資總額
        salTotal number;
    begin
        --打開部門遊標,循環部門
        open cdept;
        --進入外循環
        loop
        fetch cdept into pdep;
        exit when cdept%notfound;
            --初始化count
            count1 := 0;
            count2 := 0;
            count3 := 0;
            --獲取每個部門員工總工資
            select sum(sal) into salTotal from emp where deptno=pdep;
            --打開工資遊標,循環判斷工資,累計疊加人數
            open cemp(pdep);
    
            --進入內循環
            loop
            fetch cemp into psal;
            exit when cemp%notfound;
                if psal < 3000 then
                    count1 := count1+1;
                elsif psal >= 3000 and psal < 6000 then
                    count2 := count2+1;
                else
                    count3 := count3+1;
                end if;
            end loop;
            close cemp;
            --把結果插入到msg表中
            insert into msg values(pdep,count1,count2,count3,nvl(salTotal,0));
        end loop;
        close cdept;
        dbms_output.put_line('完成');
    end;
    
  8. 存儲過程和存儲函數

    • 存儲過程
      指存儲在數據庫中供所有用戶程序調用的子程序叫存儲過程。

      例:給指定員工漲100工資,打印漲前和漲後工資
      先創建存儲過程raiseSalary

      --創建存儲過程raiseSalary,eno爲形參,in表示輸入,number表示實參類型
      create or replace procedure raiseSalary(eno in number)
      as
          --定義變量保存前的薪水
          psal emp.sal%type;
      begin
          --得到漲後薪水
          update emp set sal = sal + 100 where empno=eno;
          dbms_output.put_line('漲前:'||psal||'  漲後:'||(psal+100));
      end;
      

      再調用存儲過程raiseSalary

      begin
        --給形參賦值
        raisesalary(eno => 7369);
        commit;
      end;
      
    • 存儲函數
      函數爲一個命名的存儲程序,可帶參數,並返回一個計算值。使用方式和存儲過程類似,但必須要有一個return子句,用於返回函數值。

      例:查詢某個員工的年收入

      先創建存儲函數queryEmpIncome

      --創建存儲函數queryEmpIncome,形參爲eno,in表示輸入,形參類型爲number
      create or replace function queryEmpIncome(eno in number)
      --返回的值的類型
      return number
      as
          --定義變量
          psal emp.sal%type;
          pcomm emp.comm%type;
      begin
          select sal,comm into psal,pcomm from emp where empno=eno;
          --返回年收入
          return psal*12+nvl(pcomm,0);
      end;
      

      再調用存儲函數

      begin
        :result := queryempincome(eno => 7369);
      end;
      
    • 過程和函數中的in/out
      一般來講,存儲過程和存儲函數的區別在於函數可以有一個返回值;而過程沒有返回值。

      但是,存儲過程和存儲函數都可以通過out來指定一個或多個輸出參數。我們可以利用out參數,在存儲過程和存儲函數返回多個值。

      故,在一般原則下,只返回一個值就用存儲函數,返回多個值就用存儲過程。存儲函數是舊版本的,爲了兼容才保留下來的。

      例:查詢某個員工的姓名,月薪,職位。
      先創建存儲過程queryEmpInfo

      create or replace procedure queryEmpInfo(eno in number,pename out varchar2,psal out number,pjob out varchar2)
      as
      begin
          select ename,sal,job into pename,psal,pjob from emp where empno = eno;
      end;
      

      再調用queryEmpInfo

      begin
        queryempinfo(eno => 7369,
                     pename => :pename,
                     psal => :psal,
                     pjob => :pjob);
      end;
      
    • 在out參數中使用遊標(適用於有很多輸出out時)

      例:查詢某個部門中所有員工的信息

      先申明包結構包頭

      --包頭
      CREATE OR REPLACE PACKAGE MYPACKAGE AS 
      
        type empcursor is ref cursor;
        procedure queryEmpList(dno in number,empList out empcursor);
      
      END;
      

      再創建包體

      --包體
      CREATE OR REPLACE PACKAGE BODY "MYPACKAGE" AS
      
        procedure queryEmpList(dno in number,empList out empcursor) AS
      
        BEGIN
      
          open empList for select * from emp where deptno=dno;
      
        END;
      
      END;
      

Java操作Oracle

  1. 連接Oracle數據庫

    • 先導入連接oracle的jar包
    • 註冊驅動
    • 連接數據庫
    • 關閉連接

    JDBCUtils.java

    package demo;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class JDBCUtils {
    
        private static String driver = "oracle.jdbc.OracleDriver";
        private static String url = "jdbc:oracle:thin:@127.0.0.1:1521/xe";
        private static String user = "scott";
        private static String password = "TIGER";
    
        static{
            //註冊驅動
            try {
                Class.forName(driver);
                //DriverManager.registerDriver(driver);
            } catch (ClassNotFoundException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    
        //連接數據庫
        public static Connection getConnection(){
            try {
                return DriverManager.getConnection(url, user, password);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        //關閉連接
        public static void release(Connection conn,Statement st,ResultSet rs){
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    rs = null;//-----> Java GC
                }
            }
            if(st != null){
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    st = null;
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    conn = null;
                }
            }       
        }
    }
    
  2. 測試操作oracle的存儲過程

    @Test
    public void testProcedure(){
        // {call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{call queryEmpInfo(?,?,?,?)}";
    
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //對於in參數,賦值
            call.setInt(1, 7839);
    
            //對於out參數,申明
            call.registerOutParameter(2, OracleTypes.VARCHAR);
            call.registerOutParameter(3, OracleTypes.NUMBER);
            call.registerOutParameter(4, OracleTypes.VARCHAR);
    
            //執行
            call.execute();
    
            //取出結果
            String name = call.getString(2);
            double sal = call.getDouble(3);
            String job = call.getString(4);
    
            System.out.println(name+"\t"+sal+"\t"+job);         
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }
    }
  3. 測試操作oracle的存儲函數

    @Test
    public void testFunction(){
        //{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{?=call queryEmpIncome(?)}";
    
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //對於out參數,聲明,第一個?代表返回值
            call.registerOutParameter(1, OracleTypes.NUMBER);
    
            //對於in參數,賦值,第二個?代表輸入的值
            call.setInt(2, 7839);
    
            call.execute();
    
            //取出結果
            double income = call.getDouble(1);
            System.out.println(income);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }       
    }
    
  4. 測試操作oracle遊標引用的存儲過程

    @Test
    public void testCursor(){
        String sql = "{call MYPACKAGE.QUERYEMPLIST(?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //對於in參數,賦值
            call.setInt(1, 20);
    
            //對於out參數,申明
            call.registerOutParameter(2, OracleTypes.CURSOR);
    
            //執行
            call.execute();
    
            //取出結果,cursor相當於集合
            rs = ((OracleCallableStatement)call).getCursor(2);
            while(rs.next()){
                String name = rs.getString("ename");
                double sal = rs.getDouble("sal");
                String job = rs.getString("job");
                System.out.println(name+"\t"+sal+"\t"+job);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, rs);
        }       
    
    }
    

觸發器

數據庫觸發器是一個與表相關聯的、存儲的PL/SQL程序。每當一個特定的數據操作語句(Insert,update,delete)在指定的表上發出時,Oracle自動地執行觸發器中定義的語句序列。

  1. 觸發器的作用

    • 數據確認
    • 實施複雜的安全性檢查
    • 做審計,跟蹤表上所做的數據操作(相當於日誌)
    • 數據的備份和同步
  2. 觸發器的類型

    • 語句級觸發器
      在指定的操作語句操作之前或之後執行一次,不管這條語句影響了多少行。

    • 行級觸發器(for each row)
      觸發語句作用的每一條記錄都被觸發。在行級觸發器中使用:old和:new僞記錄變量,識別值的狀態。

  3. 觸發器語法

    CREATE  [or REPLACE] TRIGGER  觸發器名
       {BEFORE | AFTER}
       {DELETE | INSERT | UPDATE [OF 列名]}
       ON  表名
       [FOR EACH ROW [WHEN(條件) ] ]
    declare
        ……
    begin
       PLSQL塊 
    End 觸發器名;
    
  4. 觸發器具體應用

    例1:禁止在非工作時間插入新員工到emp2表

    詳細設計:

    1. 週末:to_char(sysdate,’day’) in (‘sunday’,’saturday’)
    2. 上班前,下班後(9am~18pm):to_number(to_char(sysdate,’hh24’)) not between 9 and 17
    3. 語句級

    代碼實現:

    CREATE OR REPLACE TRIGGER  securityemp
        BEFORE INSERT
      ON EMP2
    declare
    begin
       if to_char(sysdate,'day') in ('sunday','saturday') or to_number(to_char(sysdate,'hh24')) not between 9 and 17 THEN
                --禁止insert操作,在插入之前就手動拋出錯誤
                raise_application_error(-20001,'禁止在非工作時間插入新員工!');
         end if;
    End securityemp;
    

    例2:漲後的薪水不能少於漲前的薪水(數據的確認)

    詳細設計:

    1. if 漲後的薪水 < 漲前的薪水
    2. 行級

    代碼實現:

    create or replace trigger checksalary
    before update
    on emp
    for each row
    begin
      --if 漲後的薪水 < 漲前的薪水 then
      if :new.sal < :old.sal then
        raise_application_error(-20002,'漲後的薪水不能少於漲前的薪水.漲前:'||:old.sal||'   漲後:'||:new.sal);
      end if;
    end;
    

    例3:限制每個部門人數不超過10人

    詳細設計:

    1. 用遊標存儲所有部門
    2. 循環判斷每個部門人數+1後是否超過10人
    3. 超過10人拋出錯誤

    代碼實現:

    CREATE OR REPLACE TRIGGER  checkEmpNumber
        BEFORE INSERT
      ON EMP
    declare
    cursor cemp is select deptno from dept;
    cdept dept.deptno%type;
    countEmp number;
    BEGIN
      open cemp;
        loop
            fetch cemp into cdept;
            exit when cemp%notfound;
            select count(*) into countEmp from emp where deptno = cdept;
            if countEmp+1 > 10 then
                raise_application_error(-20003,'部門:'||cdept||'  員工已有10人!');
            end if;
        end loop;
        close cemp;
    END;
    

    注:手動拋出的錯誤代碼必須在[-20001,-20999]。

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