Oracle學習筆記

Oracle的知識點總結

語法部分主要補充與MySQL不同的地方

1. 連接符||

--需求1:查詢出員工的名字,要求顯示的員工名字前面加上“姓名:”的字符串,顯示結果參考:姓名:scott
SELECT '姓名:'||ename FROM emp;

--需求2:將和員工的編號和員工的姓名都放在一個結果字段中顯示。
SELECT empno||'>>'||ename FROM emp;

注意:單引號表示字符串,雙引號用于別名

2. 偽表-dual

DUAL 是一個‘偽表’(也稱之為萬能表),可以用來測試函數和表達式

--查詢顯示當前日期
SELECT SYSDATE FROM dual;
SELECT 1+1 FROM dual;

3.過濾語句查詢where

--需求1:查詢關于KING這個人的記錄。
SELECT * FROM emp WHERE ename = 'KING';

--需求2:查詢入職日期是1987/4/19的員工的信息。
SELECT * FROM emp WHERE hiredate = to_date('1987/4/19','yyyy/MM/dd');

4. 轉義字符-Escape

--需求1:查詢名稱是帶有”x”字符的員工的記錄信息。
SELECT * FROM emp WHERE ename LIKE '%x%';
--需求2:查詢員工名稱中含有下劃線(“_”)的員工.
SELECT * FROM emp WHERE ename LIKE '%|_%' ESCAPE '|';
--需求3:查詢姓名是4個字符的員工的信息。
SELECT * FROM emp WHERE ename LIKE '____';

escape用于轉義特殊字符.在需要轉義的地方前加上escape規定的轉義符

5. 單行函數

5.1 字符函數

img01.png
1. 字符函數
    1. 大寫 upper
    2. 小寫 lower
    3. 首字母大寫 initcap
    
    select upper('hello') from dual;
    select lower('HELLO') from dual;
    select initcap('i love you') from dual;
    
2.  字符控制函數
    1.  concate / || 連接
    2.  substr 截取
    3.  length 長度
    4.  instr  第幾個
    5.  trim  去除首位空格/指定字符
    6.  replace  替換

    select concat('hello',' world') from dual;--hello world
    select substr('helloworld',3,6) from dual;--llowor,第幾個開始截,包頭不包尾
    select length('hello') from dual;--5
    select instr('hello','h') from dual;--1,第幾個
    select trim('H' from 'Hello WorHldH') from dual;--ello WorHld去掉首尾H
    select trim('  h h hhhh  ') from dual;--h h hhhh去掉首尾空格
    select replace('java','a','e') from dual;--jeve

5.2 數字函數

  • round 四舍五入

  • trunc 截斷

  • mod 求余

      select round(13.16,1) from dual;--13.2
      select trunc(13.16,1) from dual;--13.1
      select mod(20,3) from dual;--2
    

5.3 日期函數

img02.png
需求1:計算員工的工齡(工齡:當前的日期和入職的日期的差),要求分別顯示員工入職的天數、多少月、多少年。
select round(sysdate-hiredate)||'天' 入職天數,trunc(months_between(sysdate,hiredate),1)||'月' 入職月數,
    trunc(months_between(sysdate,hiredate)/12,1)||'年' 入職年數 from emp;

需求2:查看當月最后一天的日期。
select to_char(last_day(sysdate),'yyyy-MM-dd') from dual;--2017-07-31,sql語句不區分大小寫,MM是因為習慣

需求3:查看指定日期的下一個星期天或星期一的日期。(next_day(基礎日期,星期幾))
select to_char(next_day(sysdate,1),'yyyy-MM-dd') from dual;--下個星期天的日期,2017-07-23
select to_char(next_day(sysdate,2),'yyyy-MM-dd') from dual--下個星期天的日期,2017-07-24

//超過12點返回第二天的日期,否則返回當天的日期
select round(sysdate) from dual;

5.4 轉換函數

to_char : 轉換為字符格式
to_date : 轉換為日期格式
to_number : 轉換為數字格式

需求1:顯示今天的完整日期,結果參考:“2015-07-06 11:07:25”。
select to_char(sysdate,'yyyy-MM-dd hh24:mi:ss') from dual;--2017-07-17 21:05:21

需求2:顯示今天是幾號,不包含年月和時間,結果參考:“8日”。
select to_char(sysdate,'dd')||'日' from dual;--8日

需求3:顯示當月最后一天是幾號,結果參考:”30“。
select to_char(last_day(sysdate),'dd') from dual;--31

需求4:xiaoming的入職日期是2015-03-15,由于其入職日期當時忘記錄入,現在請將其插入到emp表中。
update emp set hiredate=to_date('2015-03-15','yyyy-MM-dd') where ename ='xiao_ming';

需求5:查看2015年2月份最后一天是幾號,結果參考“28“
select to_char(last_day(to_date('2015-02','yyyy-MM')),'dd') from dual;--28

---------------------------------------------------------

和java不同,Oracle的日期格式對大小寫不敏感。

日期格式的常見元素:

img03.png
--需求:查看顯示今天是星期幾
select to_char(sysdate,'day') from dual;--monday

數字格式的常見元素:

img04.png
9代表任意數字,可以不存在。0代表數字,如果該位置不存在,則用0占位。

需求:查詢員工的薪水,格式要求:兩位小數,千位數分割,本地貨幣代碼。

select to_char(sal,'L99990.00') from emp;--$1600.00

5.5 濾空函數

1. nvl(a,c),當a為null的時候,返回c,否則,返回a本身。
2. nvl2(a,b,c),當a為null的時候,返回c,否則返回b
    其中,nvl2中的2是增強的意思,類似于varchar2。
3. nullif(a,b),當a=b的時候,返回null,否則返回a
4. coalesce(a,b,c,d),從左往右查找,當找到第一個不為null的值的時候,就顯示這第一個有值的值。

--需求:查詢員工的月收入(基本薪資+獎金)
select ename 員工姓名,sal+nvl(comm,0) from emp where sal is not null;

SELECT coalesce(NULL,NULL,1,2) FROM dual;--1,返回第一個不為空的值

5.6 條件表達式

  • CASE 表達式

    表達式1:
    case job
        when 條件 then 結果
        when 條件 then 結果
        ...
        else 結果
    end

    表達式2:
    case
        when job=條件 then 結果
        when job=條件 then 結果
        ......
        else 結果
    end
  • DECODE 函數
    
    decode(job,條件1,結果1,條件2,結果2...,'其他結果')


--需求:要將工種job轉換為中文 
select ename,case
  when job='CLERK' then '辦事員'
  when job='SALESMAN' then '銷售人員'
  else '閑雜人等'
    end
from emp;

select ename,decode(job,'CLERK','辦事員','SALESMAN','銷售人員','閑雜人等') from emp;

decode只能用于等于的情況,如果要比較大小進行條件判斷,就只能用case表達式。

-------------------------------------------------------

需求:查看公司員工的工資情況,要求顯示員工的姓名、職位、工資、以及工資情況。
如果是工資小于1000,則顯示“工資過低”,工資大于1000小于5000為“工資適中”,工資大于5000的,則顯示“工資過高”:
    select ename,case
      when sal<1000 then '工資過低'
        when sal>=1000 and sal<5000 then '工資適中'
          when sal>=5000 then '工資過高'
            else '工資未知'
              end "薪資等級"
              from emp;

6. 多行函數

多行函數也稱之為分組函數、聚集函數。

就是把多行的值匯聚計算成一個值。

count,max,min,avg,sum :多行函數會自動濾空。

count(主鍵)效率很高。主鍵自動會有索引(提升查詢效率的),這個效率最高。
count(*)全表字段掃描,效率低,但現在的數據庫都對此做了優化,底層根據主鍵
count(1)效率高,如果沒有索引,這個效率比較高

--需求:查詢所屬部門號大于等于20的員工信息。(無法使用having子句)
select * from emp where deptno >= 20;
--需求:查詢平均工資大于2000的部門信息,要求顯示部門號和平均工資
select deptno,avg(sal) from emp  group by deptno having avg(sal)>=2000;

--需求1:查詢顯示各個部門的平均薪資情況,并且按照部門號從低到高排列。
select d.deptno,d.dname,t.s from dept d,(select deptno,avg(sal) s from emp group by deptno) t where d.deptno=t.deptno order by deptno;

--需求2:查詢顯示各個部門的不同工種的平均薪資情況,并且按照部門號從低到高排列。
select deptno,job,avg(sal) from emp group by job,deptno order by deptno;

sql語句優化:加上前綴效率高。

7. 多表查詢

7.1 內連接

--需求:查詢一下員工信息,并且顯示其部門名稱
select e.ename,d.dname from emp e,dept d where e.deptno=d.deptno;

顯式內連接:
select e.ename,d.dname from emp e inner join dept d on e.deptno=d.deptno;

7.2 左外連接

--查詢"所有"員工信息,要求顯示員工號,姓名 ,和部門名稱--要求使用左外連接
select e.empno,e.ename,d.dname from emp e left join dept d on e.deptno=d.deptno;
--Oracle特有語法:把+號看成擴展補充數據就就容易理解了
select e.empno,e.ename,d.dname from emp e,dept d where e.deptno=d.deptno(+);

7.3 右外連接

 --查詢“所有”部門及其下屬的員工的信息。--右外連接
select e.empno,e.ename,d.dname from emp e right join dept d on e.deptno=d.deptno;
--Oracle特有語法(+)
select e.empno,e.ename,d.dname from emp e,dept d where e.deptno(+)=d.deptno;

7.4 練習

--1.查詢員工信息,要求同時顯示員工和員工的領導的姓名
select t1.empno,t1.ename,t2.ename from emp t1,emp t2 where t1.mgr=t2.empno;
select e.empno,e.ename,e.mgr,t2.ename from emp e,(select t.empno,t.ename from emp t) t2 where e.mgr=t2.empno;

--2.查詢“所有”員工信息,要求同時顯示員工和員工的領導的姓名(所有--左外連接)
select t1.empno,t1.ename,t2.ename from emp t1 left join emp t2 on t1.mgr=t2.empno;

--需求:查找工作和'SMITH' 'ALLEN' 這兩個人的工作一樣的員工信息
select * from emp where job in (select job from emp t2 where t2.ename='SMITH' or t2.ename='ALLEN');

--需求:查找工作和'SMITH' 'ALLEN' 這兩個人的工作不一樣的員工信息
select * from emp where job not in(select job from emp t2 where t2.ename='SMITH' or t2.ename='ALLEN');

--需求:查詢工資比30號部門任意一個員工的工資高的員工信息。
select * from emp where sal>(select min(sal) from emp where deptno=30);

--需求:查詢工資比30號部門所有員工的工資高的員工信息。
select * from emp where sal>(select max(sal) from emp where deptno=30);

8. 偽列

rownum、rowid

8.1 rownum 行號

查詢操作時由ORACLE為每一行記錄自動生成的一個編號

行號排序:


    --需求:查詢出所有員工信息,按部門號正序排列,并且顯示默認的行號列信息。
    select rownum,t.* from (select * from emp order by deptno) t;

    order by排序,不會影響到rownum的順序。rownum永遠按照默認的順序生成。
    所謂的“默認的順序”,是指系統按照記錄插入時的順序(其實是rowid)。

行號分頁:


    --需求:根據行號查詢出第四條到第六條的員工信息。
    --先子查詢出小于第六條記錄的所有員工信息(盡量讓虛表盡量小)
    select * from (select rownum r,t.* from emp t where rownum<=6) t2 where r>=4;
    
    --需求:要分頁查詢,每頁3條記錄,查詢第二頁
    /*
           pageSize=3,pnum=2
           beginRownum=(pnum-1)*pageSize+1;--4
           endRownum=pageSize*pnum;--6
    */
    --寫Oracle的分頁,從子查詢寫起,也就是說從小于等于寫起,或者說從endRownum寫起
    --select rownum r,t.* from emp t where rownum<=endRownum;
    select rownum,t.* from emp t where rownum<=6;--使虛表盡量小
    --將上面查詢出來帶有行號的表作為虛表查詢,這時rownum代表的列就代表真實的列了
    select * from (select rownum r,t.* from emp t where rownum<=6) t2 where t2.r>=4;
    
    --按照薪資的高低排序再分頁
    --1. 先排序
    select * from emp order by sal desc;
    --2.將排序后的表作為一張虛表,加上虛擬行號,將查詢出來的表再作為虛表
    select rownum r,t1.* from (select * from emp order by sal desc) t1 where rownum<=6;
    --3.最后的分頁
    select * from (select rownum r,t1.* from (select * from emp order by sal desc) t1 where rownum<=6)
     where r>=4;

    --通用
    SELECT * FROM
      (
      SELECT ROWNUM r,t.* FROM 
             (SELECT ename,job,sal FROM emp ORDER BY sal DESC) t
       WHERE ROWNUM <=endRownum ORDER BY sal DESC
      )    
    WHERE r >=firstRownum ;

8.2 rowid 記錄編號

ROWID(記錄編號):是表的偽列,是用來唯一標識表中的一條記錄,并且間接給出了表行的物理位置,定位表行最快的方式。

使用insert語句插入數據時,oracle會自動生成rowid并將其值與表數據一起存放到表行中。

--需求:刪除表中的重復數據,要求保留重復記錄中最早插入的那條。
--根據姓名分類,找出相同分類中最小的rowid
select min(rowid) from test01 group by name;
--刪除除了同一分類中除最小rowid的其他記錄
delete from test01 where rowid not in(select min(rowid) from test01 group by name);

刪除重復記錄一定要小心,如果條件有問題,就會刪錯數據.刪除之前,先用查詢查一下,看是否是目標數據。

rowid與rownum不同,它是在數據插入表格中時一起存入到表格中,只是我們平時不可見,因此在查詢時,不需要將它跟表格的其他數據查詢出來作為虛表,直接使用即可.而rownum只是查詢時才生成的,因此利用它分頁時必須將rownum與其他查詢出來的數據作為虛表進行進一步的查詢

9. 增刪改

9.1 Insert

單條插入:
insert into table values(...);

--批量插入語法(主要用于將一張表中的數據批量插入到另外一張表中)
insert into test01 select empno,ename from emp;

--拷貝表
create table test02 as select * from emp;
--拷貝表結構
create table test01 as select * from emp where 1=2;

9.2 Delete和truncate區別

  • delete逐條刪除,truncate先摧毀表,再重建 。
    • 最根本的區別是:delete是DML(可以回滾,還能閃回),truncate是DDL(不可以回滾)
    • delete不會釋放空間,truncate會(當確定一張表的數據不再使用,應該使用truncate)
    • delete會產生碎片,truncate不會。

Hwm-高水位

高水位線英文全稱為high water mark,簡稱HWM,那什么是高水位呢 ?

在Oracle數據的存儲中,可以把存儲空間想象為一個水庫,數據想象為水庫中的水。
水庫中的水的位置有一條線叫做水位線,在Oracle中,這條線被稱為高水位線(High-warter mark, HWM)。
在數據庫表剛建立的時候,由于沒有任何數據,所以這個時候水位線是空的,也就是說HWM為最低值。
當插入了數據以后,高水位線就會上漲,但是這里也有一個特性,就是如果你采用delete語句刪除數據的話,數據雖然被刪除了,
但是高水位線卻沒有降低,還是你剛才刪除數據以前那么高的水位。也就是說,這條高水位線在日常的增刪操作中只會上漲,不會下跌。

高水位對查詢有巨大的影響。而且還浪費空間。

如何解決高水位帶來的查詢效率問題?

  • 1.將表數據備份出來,摧毀表再重建(truncate table),然后再將數據導回來。
    1. 收縮表,整理碎片,可使用變更表的語句:alter table 表名 move

    查看、測試、消除高水位—了解
    --之前查看rowid
    SELECT t.*,ROWID FROM TEST t;
      
    --對表進行分析,收集統計信息(執行了收集信息的動作,user_tables表的塊字段才有數據)
    analyze table TEST compute statistics; 
    --查詢表數據的塊信息,其中blocks是高水位,empty_blocks是預申請的塊空間。
    select table_name,blocks,empty_blocks from user_tables where table_name='TEST';
    --收縮表(整理碎片),降低高水位,消除行移植和行鏈接,不釋放申請的空間
    ALTER TABLE TEST MOVE;
    
    --對表進行分析,收集統計信息(執行了收集信息的動作,user_tables表的塊字段才有數據)
    analyze table TEST compute statistics; 
    --查詢表數據的塊信息,其中blocks是高水位,empty_blocks是預申請的塊空間。
    select table_name,blocks,empty_blocks from user_tables where table_name='TEST';
    
    --之后查看rowid
    SELECT t.*,ROWID FROM TEST t;
  • 收縮表之后,高水位線下降了。
  • 收縮表之后,rowid發生了變化。
img05.png

注意:

  • move最好是在空閑時做,記得move的是會產生鎖的(如果你move的時候需要很長事件,那么別人是不能操作這張表的。排他鎖)
  • move以后記得重建index(索引存放的其實就是數據的地址信息。當數據的地址變動了,索引也會失效。)語法:ALTER INDEX 索引名字 REBUILD;

10. 對表的操作

--增加字段
alter table test01 add address varchar2(60);
--刪除字段
alter table test01 drop column address;
--修改字段type
alter table test01 modify name varchar2(100);
--修改字段名
alter table test01 rename column name to ename;
--修改表名(執行RENAME語句改變表, 視圖, 序列, 或同義詞的名稱。)
rename test01 to test1;
--刪除表
drop table test02;

11. 序列-sequence

Mysql中主鍵有自增長的特性.
Oracle中,主鍵沒有自增長這個特性.

使用序列高效的生成主鍵值。

img06.png

11.1 創建序列

在ORACLE中為序列提供了兩個偽列:
1,NEXTVAL  獲取序列對象的下一個值(指針向前移動一個,并且獲取到當前的值。)
2,CURRVAL  獲取序列對象當前的值

--創建一個簡單的序列
create sequence seq_test;
--使用序列,取出當前序列,指針跳到下一個
select seq_test.nextval from dual; 
--取出當前序列,指針不變動
select seq_test.currval from dual;

11.2 序列的應用

在插入數據的時候插入序列主鍵.
insert into test1 values(seq_test.nextval,'tomcat'||seq_test.nextval);

11.3 序列的裂縫

  1. 序列是一個共有對象,多個表都可以調用。
  2. 當插入記錄時報錯,序列對象值也被使用,下一次再使用時,序列的值就會+1

也就是說,用序列插入數據庫的值不一定是連續的。

序列出現裂縫的條件:

  • 事務回滾。
  • 系統異常。
  • 多個表同時使用同一個序列。

12. 表空間tablespace

常見表空間的分類:

  • (永久)數據表空間,主要用來永久存儲正式的數據文件。
  • 臨時數據表空間,主要用來存儲臨時數據的,比如數據的排序、分組等產生的臨時數據,不能存放永久性對象。
  • UNDO表空間,保存數據修改前的鏡象

臨時表空間和UNDO表空間的異同:

相同之處:兩者都不會永久保存數據。
不同之處:
UNDO表空間用于存放UNDO數據,當執行DML操作時,oracle會將這些操作的舊數據寫入到UNDO段,以保證可以回滾和事務隔離讀取等,主要用于數據的修改等;
而臨時表空間主要用來做查詢和存放一些緩沖區數據。

12.1 創建表空間

表空間的創建一般是由DBA來操作完成的,而且需要管理員權限

三種表空間中,UNDO表空間通常是由Oracle自動化管理的,而另外兩種表空間則一般需要手動創建。

永久表空間語法:


    create tablespace tablespace_name
        [datafile datafile1,[datafile 2]…]
        [logging | nologging]
        [online|offline]
        [extent_management_clause]

    參數:
        * tablespace_name:表空間名稱隨意,但最好遵循一定的規范,如tbl_itdream_dat、tbl_itdream_tmp等。

        * datafile : 表空間的類型

        * datafile1 : 數據文件需要有如下格式:文件名 SIZE 初始文件大小 [autoextend off| on] [maxsize|next size maxSize size]
            * 文件名是數據文件的路徑名,可以是絕對路徑,也可以是相對路徑,如“路徑\xxx.dbf”,注意路徑必須先建立好。
            * 初始化文件大小,是數據文件剛建立起來的時候所占物理磁盤空間的大小
            * autoextend,是否自動擴展數據文件的大小,OFF表示關閉自動擴展,數據文件只能是初始大小,ON表示開啟自動擴展,當數據文件超過初始大小的時候,會自動增大。默認值為OFF。
            * 如果設置自動擴展,則需要設置最大值MAXSIZE,如設置2000m,當然也可以設置為UNLIMITED,表示無限表空間。
                如果要指定每次擴展的大小,可以使用NEXT SIZE MAXSIZE SIZE語法,表示每次擴展多少尺寸,最大能擴展到多大(大小上限)。

    * [logging | nologging]該子句用來聲明這個表空間上所有的用戶對象的日志屬性,默認為logging

    * [online|offline]表空間的狀態,online表示表空間創建后立即有效,offline表示表空間創建后暫時無效,即不能使用,只有設置為online后才有效,默認值為online。

    * extent_management_clause表空間如何管理范圍,推薦設置為本地管理,值為extent management local.生產環境推薦本地管理。

臨時表空間語法:


    --創建臨時數據表空間
    create temporary tablespace tablespace_name
    tempfile datafile1,[datafile 2]…
    extent_management_clause

    臨時數據表空間的數據文件一般不需要指定最大值,Oracle對其采用了貪吃算法策略,因此,該表空間會自動逐漸增大。
    其他參數見永久表空間。
    臨時表空間默認是不記日志的
        

創建表空間:


    最簡語法:
    --永久數據表空間和臨時數據表空間的建立。
    create tablespace tbl_test1_dat
    datafile 
    'c:/tbl_test1_dat01.dbf' size 100M
    autoextend on next 5M maxsize 2000M
    extent management local;
    
    --創建臨時數據表空間。
    create temporary tablespace tbl_test2_tmp
    tempfile 
    'c:/tbl_test1_tmp01.dbf' size 20M
    extent management local;
img07.png

注意:實際開發中,不要用最簡化的方式來進行表空間的創建。

參考1:

--創建數據表空間
CREATE TABLESPACE TBS_CSP_BS_DAT 
DATAFILE '/dev/rlv_dat001' SIZE 2000M REUSE AUTOEXTEND OFF,
         '/dev/rlv_dat002' SIZE 2000M REUSE AUTOEXTEND OFF   
LOGGING
ONLINE
PERMANENT
EXTENT MANAGEMENT LOCAL;
--創建臨時數據表空間
CREATE TEMPORARY TABLESPACE TBS_CSP_BS_TMP
TEMPFILE '/dev/rlv_dat009' SIZE 2000M REUSE AUTOEXTEND OFF
EXTENT MANAGEMENT LOCAL UNIFORM SIZE 10M;  

注:PERMANENT是顯式的指定創建的是永久的表空間,用來存放永久對象。默認值。

參考2:

--創建數據表空間
create tablespace tbs_user_data
logging
datafile 'D:\oracle\oradata\Oracle9i\user_data.dbf'
size 50m
autoextend on
next 50m maxsize 20480m
extent management local;
--創建臨時數據表空間
create temporary tbs_user_temp  
tempfile 'D:\oracle\oradata\Oracle9i\user_temp.dbf' 
size 50m  
autoextend on  
next 50m maxsize 20480m  
extent management local;  

12.2 刪除表空間

drop tablespace 表空間名 [including contents and datafile]

----刪除表空間以及下面所有數據和數據文件(全刪,寸草不生)
drop tablespace tbl_test2_tmp including contents and datafiles; 

如果不加后面的including...,則只是將表空間進行了邏輯刪除(Oracle無法管理使用這個表空間了,但數據文件還存在)。

13. 用戶與權限

SYS 帳戶(數據庫擁有者):
    擁有 DBA 角色權限
    擁有 ADMIN OPTION  的所有權限
    擁有 startup, shutdown, 以及若干維護命令
    擁有數據字典

system賬戶:
    擁有 DBA 角色權限.

Sys和system賬戶的區別:
    sys用戶是數據庫的擁有者,是系統內置的、權限最大的超級管理員帳號。
    system用戶只是擁有DBA角色權限的一個管理員帳號,其實它還是歸屬于普通用戶。

13.1 創建用戶(在dba權限下)

* 創建用戶的語句
    create user  用戶名
    identified by  密碼(不要加引號)
    default tablespace 默認表空間名 quota 5M on 默認表空間名
    [temporary tablespace 臨時表空間名]
    [profile 配置文件名]                                          //配置文件
    [default role 角色名]                                        //默認角色
    [password expire]                                             //密碼失效
    //如果設置失效,那么第一次登錄的時候,會提醒你更改密碼。
    [account lock]                                                   //賬號鎖定(停用)

* 修改用戶
    alter user 用戶名 identified by 密碼 quota 10M on 表空間名
    alter user 用戶名 account lock/unlock

* 刪除用戶
    drop user 用戶名 [cascade].如果要刪除的用戶中有模式對象,必須使用cascade.
img08.png
一般企業開發中,建表要手動指定表空間,可以讓不同模塊、不同功能的對象存儲在不同的數據文件中,可以提高性能。

13.2 刪除用戶

--刪除用戶及其下面所有的對象

drop user itcasttest cascade;

每個數據庫用戶帳戶具備:
    一個唯一的用戶名
    一個驗證方法
    一個默認的表空間
    一個臨時表空間
    權限和角色(dba用戶授權)
    (每個表空間的配額.)

13.3 配置角色和權限

此時,用戶jack雖然創建,但沒有任何權限,連登陸都不能成功.因此需要使用dba用戶授予jack用戶權限。

Oracle內置有大量的權限,其中常見的權限有:

img09.png

但是一個個的添加權限太過于麻煩,通過Oracle的預定義內置角色就可以實現將普通用戶的權限賦予給一個用戶。

img10.png
普通用戶就選擇:connect和Resource角色。
管理員用戶選擇:connect和Resource、dba角色。

--給jack用戶授予普通用戶的權限  
grant connect,resource to jack;

-------------------------------------------------------------------------

建立一個普通用戶的過程:
    1. create user ...創建用戶(指定表空間)
    2. 賦予權限(connect,resource)
13.4 操作jack用戶
--創建序列sequence成功
create sequence seq_test01; --序列名,其他使用默認值
select seq_test01.nextval from dual;

--創建表空間tablespace失敗
create tablespace tbs_jack_dat --表空間名
datafile --表空間類型
'c:/tbs_jack_dat01.dbf' size 50M --指定表空間數據文件的存儲位置及大小
autoextend on --開啟表空間文件自動增長
next 5M maxsize 1000M --每次增長5M,最大1000M
extent management local; --本地管理表空間

jack用戶創建表空間失敗,原因是表空間創建的權限不夠,表空間默認有dba權限的用戶才能創建。
13.5 對象權限

Oracle用戶的權限分為兩種:

系統權限(System Privilege): 允許用戶執行對于數據庫的特定行為,例如:創建表、創建用戶等

對象權限(Object Privilege): 允許用戶訪問和操作一個特定的對象,例如:對其他方案下的表的查詢

實現跨域訪問:

跨域訪問也稱之為跨用戶訪問、跨方案訪問,訪問的方式為:用戶名.對象名,

如在itcast用戶下訪問scott用戶下的emp表的數據:
    Select * from scott.emp;

--訪問失敗。原因:沒有對象訪問權限。

登陸scott用戶,授予emp表的查詢權限給jack用戶

--scott用戶授予emp表的查閱權限給jack
grant select on emp to jack;

--跨域訪問成功。
select * from scott.emp;

注意:
賦權的時候,只能是自己擁有的權限、或者該權限是可以傳遞的,才可以將其賦予別人。

14. 視圖View

需求:jack用戶現在只需要查詢10部門的員工數據就行了,scott也不想將所有數據都開放給jack用戶。

14.1 視圖的概念

概念:

  • 視圖是一種虛表.
  • 視圖建立在已有表的基礎上, 視圖賴以建立的這些表稱為基表。
  • 向視圖提供數據內容的語句為 SELECT 語句, 可以將視圖理解為存儲起來的 SELECT 語句.
  • 視圖向用戶提供基表數據的另一種表現形式

作用:

  • 限制數據訪問
  • 簡化復雜查詢(將比較復雜并且經常要查詢的數據封裝成視圖保存起來,以后只需要查這張表即可)
  • 提供數據的相互獨立
  • 同樣的數據,可以有不同的顯示方式
  • 視圖不能提高性能

14.2 語法

img11.png

視圖只能創建、替換和刪除,不能修改。

14.3 視圖的操作

創建視圖:

    
    嘗試在jack用戶創建視圖:(失敗)
        --復制scott用戶的emp表
        create or replace table emp as select * from scott.emp;
        --在jack用戶創建視圖View
        create view v_jack_emp --視圖的名稱
        as select empno 編號,ename 姓名 from emp -- 
        with read only;
    
    創建jack用戶的視圖失敗,原因:權限不夠,說明視圖的創建權限也需要dba權限.

    登陸system用戶,賦予jack用戶dba權限.
    --賦予jack用戶dba權限
    grant dba to jack;

    --jack用戶再次創建視圖View
    create or replace View v_jack_emp --視圖的名稱
    as select empno 編號,ename 姓名 from emp where deptno=10 --視圖顯示內容
    with read only;
    創建成功。通過視圖查詢。

    create or replace 創建或替換
img12.png

刪除視圖:


    --刪除視圖v_jack_emp
    drop view v_jack_emp;

視圖默認是可以修改的,但我們一般只用視圖來提供查詢功能,因此設置它為只讀,with read only

14.4 視圖的跨域訪問

需求:在scott用戶下訪問jack用戶的視圖v_jack_emp

視圖和表類似,如果直接在scott用戶訪問該視圖肯定是沒有權限的,因此jack用戶要賦予scott查詢該視圖的權限。

--因為剛剛刪除了視圖,這里再次創建
    create or replace view v_jack_emp   --創建或替換視圖v_jack_emp
    as select empno 編號,ename 姓名 from emp where deptno=10        --視圖的內容
    with read only;     --視圖只讀

--jack用戶賦予scott用戶視圖查詢權限
grant select on v_jack_emp to scott;

--登陸scott用戶執行查詢
select * from jack.v_jack_emp;

這樣就能控制讓其他用戶只能訪問我想讓他看到的內容,不給它查詢表的權限,只給他查詢視圖的權利。

實際上,我們查詢的表可能不是真正的表,而是視圖,而且還是只讀的。
視圖還可以屏蔽篩選/修改不同字段、字段名稱等(別名),因此,你看到的時候的字段也未必是真實表中存在的!

14.5 視圖小結

視圖和表的區別:

視圖是實體表的映射,視圖和實體表區別就是于視圖中沒有真實的數據存在。

什么時候使用視圖:

1,在開發中,有一些表結構是不希望過多的人去接觸,就把實體表映射為一個視圖。
2,在項目過程中,程序人員主要關注編碼的性能、業務分析這方面。對于一些復雜的SQL語句,提前把這些語句封裝到一個視圖中,供程序人員去調用

注意:查詢的對象(表)他可能不是一張的表,可能是視圖;你看到的視圖的字段可能也不是真實的字段。

15. 同義詞SYNONYM

需求:想偽裝一下這個視圖的名字不被其他人知道,或者是嫌調用的這個對象名字太長,怎么辦?

15.1 同義詞的概念

同義詞就是(對象的)別名,可以對表、視圖等對象起個別名,然后通過別名就可以訪問原來的對象了。

作用:

  • 方便訪問其它用戶的對象
  • 縮短對象名字的長度

15.2 語法

create [public] synonym for Object;  --Object指Oracle對象

15.3 操作同義詞

創建同義詞:

首先,我創建一個tom用戶,檢測同義詞synonym的創建權限
--創建tom用戶
create user tom --用戶名稱
identified by orcl --密碼
default tablespace tbs_itdream_dat --默認表空間
temporary tablespace tbs_itdream_tmp; --臨時表空間
--授權
grant connect,resource to tom;
--創建tom01表格
create table tom01(id number);
insert into tom01 values(1);
select * from tom01;
--為tom01表格創建同義詞
create synonym t1 for tom01;

創建失敗,權限不夠.說明同義詞synonym的創建不夠權限,
下面刪除tom用戶,繼續使用jack用戶進行操作.
drop user tom cascade;  //必須有dba權限的用戶才能刪除
------------------------------------------------------------------------
在擁有dba權限的jack用戶中為v_jack_emp視圖創建同義詞synonym:
--為視圖v_jack_emp創建同義詞
create synonym e for v_jack_emp;
--jack通過同義詞查詢視圖
select * from e;
--scott用戶通過同義詞跨域訪問視圖
select * from jack.e;
select * from jack.v_jack_emp;

一個小失誤:

不小心,將dba權限給刪除了,重裝了一次oracle,重建jack用戶

--創建默認表空間
create tablespace tbs_jack_dat --創建永久表空間tbs_jack_dat,
datafile --表空間的類型
'c:/tbs_jack_dat01.dbf' size 100M --指定表空間物理文件的存儲位置及默認大小
autoextend on --開啟自動增容
next 5M maxsize 2000M --自動增容一次5M,最大2G
extent management local;--本地管理表空間

--創建臨時表空間
create temporary tablespace tbs_jack_tmp --創建臨時表空間
tempfile --表空間類型為臨時表空間
'c:/tbs_jack_tmp01.dbf' size 5M --設置臨時表空間物理文件的儲存位置及默認大小,臨時表空間無需設置自動增長,有默認的貪吃模式
extent management local; --本地管理表空間

--創建jack用戶
create user jack  --用戶名
identified by jack --密碼
default tablespace tbs_jack_dat --默認表空間
temporary tablespace tbs_jack_tmp --默認臨時表空間;

--授予權限給jack用戶
grant dba,connect,resource to jack;

--建表
create table emp as select * from scott.emp;

--------------------------------------------------
--創建視圖View
create or replace view v_jack_emp --創建視圖v_jack_emp
as select empno 編號,ename 姓名 from emp --視圖的內容
with read only; --設置視圖只讀

--給視圖創建同義詞
create synonym e for v_jack_emp;
--同義詞查詢測試(成功)
select * from e;
-----------------------------------------------------
--跨域查詢同義詞,不授權視圖,只授權同義詞查詢,測試是否能夠成功在scott用戶下查詢
--授權同義詞給scott
grant select on e to scott;

--切換scott用戶跨域查詢jack用戶的同義詞e代表的視圖v_jack_emp
select * from jack.e;--成功
select * from jack.v_jack_emp;--成功

結論:

授權視圖查詢給scott用戶,在scott用戶下通過視圖或同義詞查詢都可以。
授權同義詞查詢給scott用戶,同樣可以。

16. 索引-index

16.1 索引的概念特性和作用

數據庫中的索引相當于字典的目錄(索引)),它的作用就是提升查詢效率。

特性:

  • 一種獨立于表的模式(數據庫)對象, 可以存儲在與表不同的磁盤或表空間中。
  • 索引被刪除或損壞, 不會對表(數據)產生影響, 其影響的只是查詢的速度。
  • 索引一旦建立, Oracle 管理系統會對其進行自動維護, 而且由 Oracle 管理系統決定何時使用索引. 用戶不用在查詢語句中指定使用哪個索引。
  • 在刪除一個表時, 所有基于該表的索引會自動被刪除。
  • 如果建立索引的時候,沒有指定表空間,那么默認索引會存儲在哪個表空間.會存儲在所屬用戶默認的表空間.

作用:

  • 通過指針(地址)加速Oracle 服務器的查詢速度。
  • 提升服務器的i/o性能(減少了查詢的次數)

16.2 索引的工作原理

img13.png

16.3 操作索引

創建索引:

索引有兩種創建方式:

  • 自動創建: 在定義 PRIMARY KEY 或 UNIQUE 約束后系統自動在相應的列上創建唯一性索引。
  • 手動創建: 用戶可以在其它列上創建非唯一的索引,以加速查詢。

手動創建索引:

--scott用戶在emp表上的deptno上創建索引,
create index idx_scott_deptno on emp(deptno);

建立索引成功,說明索引的創建普通用戶的權限既可以完成。

上面索引的創建有缺陷。
--缺點:沒有指定表空間,生產環境下一般要將索引單獨指定表空間。
create index idx_emp_ename on EMP (ename) tablespace USERS;

刪除索引:
--刪除idx_scott_deptno索引
drop index idx_scott_deptno;

16.4 索引的創建場景

索引不是萬能!

以下情況可以創建索引:

  • 列中數據值分布范圍很廣.即根據這個索引分類,可以分很多類。
  • 表經常被訪問而且數據量很大 ,訪問的數據大概占數據總量的2%到4%

以下情況不要創建索引:

  • 表比較小
  • 列不經常作為連接條件或出現在WHERE子句中
  • 查詢的數據大于2%到4%
  • 表經常頻繁更新(看需求,如果表經常不斷的再更新,Oracle會頻繁的重新改動索引,反而降低了數據庫性能。但如系統日志歷史表,就必須增加索引,效率超高)

一些關于索引的問題:

1. 索引的作用是什么?
主要是提高查詢效率,減少磁盤的讀寫,從而提高數據庫性能。
2. 創建索引一定能提高查詢速度么?
未必!得看你創建的索引的合理性和語句的編寫
3. 索引創建的越多越好么?
不是!索引也是需要占用存儲空間的,過多的索引不但不會加速查詢速度,反而還會降低效率。

17. PL/SQL編程

17.1 概念和目的

PLSQL(Procedure Language/SQL)Oracle對sql語言的過程化擴展。

PLSQLSQL命令語言中增加了過程處理語句(如分支、循環等),使SQL語言具有過程處理能力。(具有編程的能力)。

17.2 PLSQL的入門Hello World

declare --用于聲明變量

begin --業務邏輯
  --輸出Hello World!
  dbms_output.put_line('Hello World!');
end;

----------------------------------------------------------------------------

--面向過程的語言
--declare --聲明部分:沒有變量,則declare可以省略
 --不需要變量聲明,則不需要寫任何東西
BEGIN--程序體的開始:編寫語句邏輯
    --在控制臺輸出一句話:dbms_output相當于system.out類,內置程序包,put_line:相當于println()方法
  dbms_output.put_line('Hello World');
    --dbms_output.put('Hello World');
end;--程序體的結束

17.3 程序結構

PL/SQL可以分為三個部分:聲明部分、可執行部分、異常處理部分。
[delare]
    聲明部分(變量、游標、例外)
begin
    邏輯執行部分(DML語句、賦值、循環、條件等)
[exception]
    異常處理部分(when 預定義異常錯誤 then)
end;
/

注意:在SQLPLUS中,PLSQL執行時,要在最后加上一個 “/”

17.4 變量聲明

declare聲明部分可以定義變量,定義變量的語法:

變量名 [CONSTANT] 數據類型;

* 普通數據類型(char, varchar2, date, number, boolean, long):
    * id number;
    * name varchar(20);
    * sex char(1);
    * birthday date;
    * married boolean := true; 直接賦值
    * salary number(7,2); 總共7位數,5位整數,2位小數


* 特殊變量類型(引用型變量,記錄型變量):
    * username emp.ename%type;  --引用型變量,即username的變量名和emp表的ename類型一樣
    * emp_rec  emp%rowtype; --記錄型變量,即一次可以存儲一行數據

17.5 賦值

普通變量賦值:

在ORACLE中有兩種賦值方式:
1,直接賦值語句      :=
2, 使用select …into … 賦值:(語法;select 值 into 變量)

-----------------------------------------------------------------------------

declare  --變量聲明
  v_id number;
  v_name varchar(20) := 'jack'; --直接賦值
  v_address varchar(60);
  v_salary number;
begin  --邏輯代碼
       --方法一:直接賦值
       v_id := 1;
       --方法二:語句賦值
       select '深圳' into v_address  from dual;
       select sal into v_salary from emp where ename='KING';
       
       --打印
       dbms_output.put_line('v_id:'||v_id||',姓名:'||v_name||',地址:'||v_address||',薪資:'||v_salary);
end;        

注意:
語句賦值,格式是select 字段 into 聲明變量 from 表 where ...

引用變量:引用表中字段的類型 (推薦使用引用類型)

%type   例:  v_ename  emp.ename%type;

--查詢并打印7839號(老大)員工的姓名和薪水
declare 
       --變量引用emp表的字段類型
       v_ename emp.ename%type;
       v_sal emp.sal%type;
begin
       --語句賦值v_ename
       select ename into v_ename from emp where empno=7839;
       --語句賦值v_sal
       select sal into v_sal from emp where empno=7839;
       --打印
       dbms_output.put_line('姓名:'||v_ename||',薪資:'||v_sal);     
end;

------------------------------------------------------------------------

引用類型的好處:

  • 使用普通變量定義方式,需要知道表中列的類型,而使用引用類型,不需要考慮列的類型
  • 使用引用類型,當列中的數據類型發生改變,不需要修改變量的類型。而使用普通方式,當列的類型改變時,需要修改變量的類型

使用%TYPE是非常好的編程風格,因為它使得PL/SQL更加靈活,更加適應于對數據庫定義的更新。


記錄型變量:

記錄型變量,代表一行,可以理解為數組,里面元素是每一字段值。

%rowtype 例: v_emp emp%rowtype;直接引用emp表所有的字段及類型
含義:v_emp 變量代表emp表中的一行數據的類型,它可以存儲emp表中的任意一行數據。

---------------------------------------------------------------------------

--查詢并打印7839號(老大)員工的姓名和薪水

    declare
       --引用類型變量
       v_emp emp%rowtype;--該變量可以存儲emp表中一行記錄的值
    begin
       --變量語句賦值
       select * into v_emp from emp where empno=7839;
       
       --打印
        dbms_output.put_line('姓名:'||v_emp.ename||',薪資:'||v_emp.sal);  
    end;

17.6 PLSQL編程—流程控制

條件結果 if
語法:
if    條件    then   結果
elsif   條件   then   結果
......
else  其他情況的結果
end if;

----------------------------------------------------------------------------
--判斷emp表中記錄是否超過20條,,10-20之間,10以下打印一句

declare 
  --聲明一個變量,存儲查詢出來的記錄數
  v_count number;
begin 
  --獲取emp表的記錄數,給v_count賦值
  select count(1) into v_count from emp;
  
  --條件判斷
  if v_count<=10 then dbms_output.put_line('記錄數小于10條');
  elsif v_count>10 and v_count<=20 then dbms_output.put_line('記錄數在10-20之間');
  else dbms_output.put_line('記錄數大于20條');
  end if;
end;
循環Loop
img14.png
在ORACLE中有三種循環:
    Loop  循環  EXIT  WHEN...條件 end loop;
    While()…loop 條件判斷循環
    For 變量 in 起始..終止  Loop

其中使用第一種,能完成其它兩種循環,因此接下來就是用Loop循環操作。
    
    --打印數字1-10
    declare
      --聲明一個變量
      v_num number := 1;
    begin
      --循環
      loop
        --輸出
        dbms_output.put_line(v_num);
        --退出循環的條件
        exit when v_num >= 10;
        v_num := v_num+1;
      end loop;
    end; 

18. 游標-Cursor

18.1 游標的概念

游標從概念上講基于數據庫的表返回結果集,也可以理解為游標就是個結果集,但該結果集是帶向前移動的指針的,每次只指向一行數據。類似與JDBC操作時返回的ResultSet結果集。

游標的主要作用:

用于臨時存儲一個查詢返回的多行數據(結果集),通過遍歷游標,可以逐行訪問處理該結果集的數據。

游標的使用方式:聲明--->打開--->讀取--->關閉

18.2 語法

1. 游標聲明:
CURSOR  游標名  [ (參數名  數據類型[,參數名 數據類型]...)]
      IS  SELECT   語句;

無參游標:
cursor c_emp is select ename from emp;
有參游標:
cursor c_emp(v_deptno emp.deptno%TYPE) is select ename from emp where deptno=v_deptno;


2. 開啟游標. 會執行查詢獲得結果集。 
Open 游標名(參數列表)
例:open c_emp; --開啟游標,執行查詢

3. 讀取游標
fetch 游標名 into 變量列表|記錄型變量
例:fetch c_emp into v_ename;--取一行游標的值到變量中。注意:v_ename必須與emp表中的ename列類型一致。(v_ename emp.ename%type;)

4. 關閉游標釋放資源
例:close 游標名
close c_emp;--關閉游標釋放資源

18.3 游標的屬性

img15.png

游標的原理:

游標剛開啟時,游標在結果集的第一條記錄之前。
當fetch取值時,指針會往前游動,并獲取游動后的后處于游標位置的值。(游標是有位置的)
注意:游動不能回頭。

因此可以使用,%notfound屬性來判斷游標是否遍歷完畢。
img17.png

18.4 操作無參游標

需求:使用游標查詢emp表中所有員工的姓名和工資,并將其依次打印出來。

引用型變量獲取游標的值:

declare 
  --聲明一個游標
  cursor c_emp_test1
         is select ename,sal from emp; 
         
  --聲明兩個變量用于接收遍歷出來的數據
  v_ename emp.ename%type;
  v_sal emp.sal%type;
begin
  --開啟游標,執行查詢
  open c_emp_test1;
  
  --使用游標,循環取值
  loop
    --獲取值,存入臨時變量時,要保證數量和類型一致.每次取出值后,指針往下移動一次
    fetch c_emp_test1 into v_ename,v_sal;
    --判斷當前游標位置是否有值,如果沒有就退出循環
    exit when c_emp_test1%notfound;
    --輸出
    dbms_output.put_line('姓名:'||v_ename||',薪資:'||v_sal);
  end loop;  

  --關閉游標,釋放資源
 close c_emp_test1;
end;

PLSQL程序運行結果:

img16.png

使用記錄型變量存值:
declare
  --聲明游標
  cursor c_emp is select * from emp;
  --記錄型變量
  v_emp emp%rowtype;--可用于存儲一行記錄的值
begin
  --開啟游標,執行查詢
  open c_emp;
  --使用游標,循環取值
  loop
    --獲取游標的值放入變量的時候,必須要into前后要對應(數量和類型)
    fetch c_emp into v_emp;
    --推出循環的條件
    exit when c_emp%notfound;
    --打印
    dbms_output.put_line('姓名:'||v_emp.ename||',薪資:'||v_emp.sal);
  end loop;
  
  --關閉游標
  close c_emp;
end;

18.5 帶參游標

需求:使用游標查詢并打印某部門的員工的姓名和薪資,部門編號為運行時手動輸入。

使用引用型變量取游標的值:

declare 
  --聲明游標--帶參數的游標:需要定一個形式參數
  cursor c_emp_dept(v_deptno emp.deptno%type)
         is select ename,sal from emp where deptno=v_deptno;
         
  --聲明變量
  v_ename emp.ename%type;
  v_sal emp.sal%type;
begin 
  --開啟游標,執行查詢--傳入參數:部門編號
  open c_emp_dept(10);
  --使用游標循環取出游標中的值
  Loop
    --取值,存入臨時變量(保證into后的數量與類型與游標的select一致)
    fetch c_emp_dept into v_ename,v_sal;
    --退出循環的條件:如果當前游標位置沒有值,就退出循環
    exit when c_emp_dept%notfound;
    --輸出
    dbms_output.put_line('姓名:'||v_ename||',薪資:'||v_sal);
  end Loop;
  
  --關閉游標釋放資源
  close c_emp_dept;
end;

使用記錄型變量取游標中的值:
declare
  --聲明一個帶參的游標
  cursor c_emp_dept2(v_deptno emp.deptno%type)
         is select * from emp where deptno=v_deptno;
         
  --記錄型變量
  v_emp emp%rowtype;
begin
  --開啟游標,執行查詢獲得結果集
  open c_emp_dept2(20);--傳入參數:部門編號20
  
  --使用游標,循環取值
  Loop
    --取值(保證數量和類型一致,因此游標使用的*)
    fetch c_emp_dept2 into v_emp;
    --跳出循環(指針所處游標位置沒有值時跳出循環)
    exit when c_emp_dept2%notfound;
    --打印
    dbms_output.put_line(v_emp.ename||'>>>'||v_emp.sal);
  end Loop;
  
  --關閉游標,釋放資源
  close c_emp_dept2;
end;

注意:

  • Found是游標有數據的判斷。如果游標剛打開,值false
  • Notfound是游標結束的判斷,如果游標剛打開,值false,只判斷游標是否結束!

19.存儲過程

19.1 概念與作用

存儲過程:就是一塊PLSQL語句包裝起來,起個名稱

相對而言:單純plsql可以認為是匿名程序。

  • plsql是存儲過程的基礎。
  • java是不能直接調用plsql的,但可以通過存儲過程這些對象來調用。

19.2 語法

create or replace procedure 過程名(參數列表)
as/is
    --聲明
begin
    PLSQL子程序體,完成邏輯操作
end 程序名;
-----------------------------------------------------------------------------

As和is是通用的。
根據參數的類型,我們將其分為3類:
    1. 不帶參數的存儲過程
    2. 帶輸入參數的存儲過程
    3. 帶輸入參數與輸出參數的存儲過程

19.3 無參的存儲過程

create or replace procedure sayHelloWorld  --沒有參數的情況下,不要加() 
as
  --聲明變量,不寫不可省略
begin
  dbms_output.put_line('Hello World!');
end sayHelloWorld;

存儲過程的測試,直接在Procedure對象右鍵選擇Test即可。

19.4 存儲過程的調用方法

如何調用執行,兩種方法:

  • 用exec(execute)命令來調用—用來測試存儲
  • 用其他的程序(plsql和java)來調用

命令調用的方式:

img21.png

程序調用:

img22.png

19.5 帶輸入參數的存儲過程

可以直接在PLSQL工具右鍵新建Procedure程序,工具幫我們自動寫好一些代碼。

--查詢并打印某個員工(如7839號員工)的姓名和薪水--存儲過程:要求,調用的時候傳入

--創建一個帶輸入參數的存儲過程,in代表這是個輸入參數,out代表是輸出參數
create or replace procedure p_queryempsal(i_empno in emp.empno%type) 
is 
  --聲明變量, 接收結果
  v_ename emp.ename%type;
  v_sal emp.sal%type;
begin
  --邏輯代碼
  select ename,sal into v_ename,v_sal from emp where empno=i_empno;
  
  --輸出打印
  dbms_output.put_line('姓名:'||v_ename||',薪資是:'||v_sal);
  
end p_queryempsal;

命令調用:

img23.png

程序調用:

img24.png

19.6 帶輸入in和輸出參數out—主要是其他程序調用

--輸入員工號查詢某個員工(7839號員工)信息,要求,將薪水作為返回值輸出,給調用的程序使用。
create or replace procedure p_queryempsal_out(i_empno in emp.empno%type,
                                               o_sal out emp.sal%type)
is
begin
  --執行查詢
  select sal into o_sal from emp where empno=i_empno;
end p_queryempsal_out;
        

--------------------------------------------------------------------------

調用(使用plsql程序調用):

--使用plsql程序調用p_queryempsal_out存儲過程
declare 
  --輸入參數值
  v_empno emp.empno%type := 7839;
  --聲明一個參數來接收輸出參數
  v_sal emp.sal%type;
  
begin
  --調用Procedure存儲過程
  p_queryempsal_out(v_empno,v_sal);--第二個參數是輸出參數,必須有變量來接受!
  --打印v_sal
  dbms_output.put_line('員工編號:'||v_empno||',薪資是:'||v_sal);
end;

注意:調用的時候,參數要與定義的參數的順序和類型一致.

----------------------------------------------------------------------------

也可以直接Test測試。

img25.png

存儲過程總結:

存儲過程的作用:主要用來執行一段程序。

  1. 在開發程序中,為了一個特定的業務功能,會向數據庫進行多次連接關閉(連接和關閉是很耗費資源)。這種就需要對數據庫進行多次I/O讀寫,性能比較低。如果把這些業務放到PLSQL中,在應用程序中只需要調用PLSQL就可以做到連接關閉一次數據庫就可以實現我們的業務,可以大大提高效率.
  2. ORACLE官方給的建議:能夠讓數據庫操作的不要放在程序中。在數據庫中實現基本上不會出現錯誤,在程序中操作可以會存在錯誤.(如果在數據庫中操作數據,可以有一定的日志恢復等功能.)

三種存儲基本應用場景:

  • 無參參數:只用來做數據處理。存儲內部寫一些處理數據的邏輯。
  • 帶輸入參數:數據處理時,可以針對輸入參數的值來進行判斷處理
  • 帶輸入輸出參數:一般用來傳入一個參數值,我想經過數據庫復雜邏輯處理后,得到我想要的值然后輸出給我。

20.存儲函數

語法:
create [or replace] function 函數名(參數列表) 
return  函數值類型
as/is
    --聲明
begin
    --程序體
    --必須有return
    return ...;
end;

20.1 存儲函數案例

--查詢某職工的總收入。
create or replace function queryempincome(f_empno in emp.empno%type) --創建一個存儲函數,傳遞參數
return number --返回值類型
as 
  --聲明一個變量查詢出來的結果返回
  v_income number;
begin
  --查詢賦值
  select sal*12+nvl(comm,0) into v_income from emp where empno=f_empno;
  --返回值
  return v_income;
end;

PLSQL程序調用:

--測試queryempincome存儲函數
declare
  --聲明一個變量接收返回的值
  v_income number;
begin
  --調用函數獲得結果
  v_income := queryempincome(7934);--傳入參數
  --打印
  dbms_output.put_line('編號:'||7934||',總收入:'||v_income);
end;

Test程序測試:

img26.png

如何選擇存儲過程和存儲函數?

原則上,如果只有一個返回值,用存儲函數,否則,就用存儲過程。
但是,一般我們會直接選擇使用存儲過程,原因是:

* 函數是必須有返回值,存儲過程可以有也可以沒有,存儲的更靈活!
* 存儲過程也可以有輸出參數,可以代替存儲函數。
* Oracle的新版本中,已經不推薦使用存儲函數了。

21. 例外

語法:

declare
    --聲明部分
begin
    --邏輯部分
excepetion
    --捕獲例外
    when 例外名 then ...
    when 例外名 then...
    when others then ...
end;

--例外
declare
  i number;
  --聲明一個引用型變量
  v_ename emp.ename%type;
  --聲明一個記錄型變量
  v_emp emp%rowtype;
begin
  --i := 1/0; zero_divide
  --i := 'abc'; value_error
  --select ename into v_ename from emp where empno = 123; no_data_found
  select * into v_emp from emp;
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 too_many_rows then dbms_output.put_line('記錄數不匹配例外');
      when others then dbms_output.put_line('發生了未知例外');
end;

21.1 自定義例外

在declare-begin中聲明一個自定義例外:
    例外名  exception;

----------------------------------------------------------------------------

--使用自定義例外
declare 
  --聲明自定義例外
  no_emp_found exception;
  --聲明游標
  cursor c_emp is select * from emp;
  v_emp emp%rowtype;
begin
  --開啟游標
  open c_emp;
  --因為要使用自定義游標,直接利用游標的屬性拋異常
  loop
    fetch c_emp into v_emp;
    if c_emp%notfound then
       raise no_emp_found;
    end if;
  end loop;
exception
  when no_emp_found then 
    dbms_output.put_line('拋出自定義例外');  
   when others then 
     dbms_output.put_line('拋出未知的例外');
     
  --關閉游標
  close c_emp;
end;

22. 使用java調用存儲過程

寫一個存儲過程p_queryempsal_out.使用java代碼調用它,并獲取到返回的值打印出來。

PLSQL代碼:
--需求:封裝存儲過程:傳入某個員工的員工編號,輸出該員工的年薪
create or replace procedure proc_getYearSal(i_empno in emp.empno%type,o_yearsal out emp.sal%type) --創建存儲過程
as 
       --聲明部分
begin
  --獲取年薪,賦值給輸出變量
  select sal*12+nvl(comm,0) into o_yearsal from emp where empno=i_empno;
end;
---------------------------------------------------------------------------

java代碼調用過程:

使用JDBC連接數據庫,傳入員工編號,獲取參數。
1. 加載數據庫驅動
2. 創建Connection連接
3. 獲取執行sql的CallableStatement對象
4. 設置參數
5. 執行sql,獲取結果集
6. 關閉資源


首先導入Oracle.jdbc的驅動包:ojdbc6.jar

準備JDBCUtils工具類:(變量也可以提取到配置文件中)

public class JDBCUtils {

    private static String driver = "oracle.jdbc.oracle.OracleDriver";
    private static String url = "jdbc:oracle:thin:@192.168.175.10:1521:orcl";
    private static String user = "jack";
    private static String password = "jack";

    static {
        try {
            Class.forName(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;// why? ---> 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;
            }
        }
    }
}


----------------------------------------------------------------------------

java代碼調用存儲過程-輸出參數返回的值是普通類型:

    @Test
    public void test() throws Exception {
        // 獲取連接對象
        Connection connection = JDBCUtils.getConnection();

        String sql = "{call proc_getYearSal(?,?)}"; // 轉義sql,從API中查詢格式即可
        // 獲取執行sql的對象CallableStatement
        CallableStatement call = connection.prepareCall(sql);

        // 設置參數,輸入參數直接set,輸出參數需要注冊,執行完之后,call對象中調用get方法獲取輸出值
        call.setInt(1, 7369);
        // 注冊輸出參數.參數1:參數的位置,參數2:參數類型
        call.registerOutParameter(2, OracleTypes.DOUBLE);

        // 執行sql
        call.execute();

        //獲取輸出參數的值
        double yearSal = call.getDouble(2);
        System.out.println("編號7369的員工的年薪為:"+yearSal+"元");
        
        //關閉資源
        JDBCUtils.release(connection, call, null);
    }

java代碼調用存儲過程-輸出參數返回的值是結果集:

--需求:封裝存儲過程,獲取emp表所有員工的編號,姓名,月薪
create or replace procedure proc_getempinfo(c_empinfo out sys_refcursor) --創建存儲過程,輸出參數是一個結果集,因此用游標封裝(sys_refcursor系統引用游標,使用時才指定查詢語句)
as
 
begin
  --系統引用游標,開啟時指定查詢條件
  open c_empinfo for select empno,ename,sal from emp;
end;

使用java代碼調用該存儲過程proc_getempinfo:
    @Test
    public void test2() throws Exception {
        // 獲取連接
        Connection connection = JDBCUtils.getConnection();
        String sql = "{call proc_getempinfo(?)}";// 轉義sql
        // 獲取執行sql的對象
        CallableStatement call = connection.prepareCall(sql);

        // 設置參數:注冊輸出參數
        call.registerOutParameter(1, OracleTypes.CURSOR);
        // 執行sql
        call.execute();

        // 獲取結果集.
        //CallableStatement沒有getCursor方法,找它的實現類對象
        //System.out.println(call.getClass()); //oracle.jdbc.driver.T4CCallableStatement
        //但是它的實現類權限是默認的而不是public的而不能使用,因此使用它的父類OracleCallableStatement中的getCursor方法
        OracleCallableStatement call2 = (OracleCallableStatement)call;
        
        ResultSet rs = call2.getCursor(1);
        while(rs.next()) {
            System.out.println("編號是:"+rs.getObject("empno"));
            System.out.println("姓名是:"+rs.getObject("ename"));
            System.out.println("月薪是:"+rs.getObject("sal"));
            System.out.println("=================================");
        }
        
        //關閉資源
        JDBCUtils.release(connection, call, rs);
    }
  • 數據庫存儲過程的編寫,注意輸出參數必須加上out
  • 誰調用存儲過程,得到游標結果集ResultSet由調用者負責關閉游標。

23. 觸發器-Trigger

23.1 概念與作用

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

換句話說:觸發器就是在執行某個操作(增刪改)的時候觸發一個動作(一段程序)。

23.2 語法

創建觸發器語法:
CREATE  [or REPLACE] TRIGGER  觸發器名
{BEFORE | AFTER}
{DELETE | INSERT | UPDATE [OF 列名]}
ON  表名
[FOR EACH ROW [WHEN(條件) ] ]
PLSQL 塊(即:as begin end)
img27.png

23.3 觸發器HelloWorld

測試:普通用戶也可以創建觸發器。

create or replace trigger tri_sayHello --創建觸發器
before --在..操作之前執行觸發器
insert --插入數據時觸發
on emp --插入emp表時觸發
declare
   --聲明部分
begin
  --邏輯部分
  dbms_output.put_line('Hello World!!!');
end;

23.3 觸發器的類型

  • 語句級觸發器(表級觸發器)
    • 在指定的操作語句操作之前或之后執行一次,不管這條語句影響了多少行 。
  • 行級觸發器(FOR EACH ROW)
    • 觸發語句作用的每一條記錄都被觸發。在行級觸發器中使用old和new偽記錄變量, 識別值的狀態。

23.4 語句級觸發器與行級觸發器的區別

--語句級觸發器:
create or replace trigger tri_yuju_test
before update
on emp
declare
begin
  dbms_output.put_line('語句級觸發器...');
end;

--行級觸發器:
create or replace trigger tri_hangji_test
before update
on emp
for each row --定義行級觸發器
declare
begin
  dbms_output.put_line('行級觸發器...');
end;

delete from emp where empno in(9527,9528);
select * from emp;

--測試:修改emp表所有員工的工資:
update emp set sal=10000;
img28.png

語句級觸發器和行級觸發器區別:

語法上:
    1.行級觸發器需要定義:for each row

表現上:
    2.行級觸發器,在每一行的數據進行操作的時候都會觸發。但語句級觸發器,對表的一個完整操作才會觸發一次。
    
簡單的說:行級觸發器,是對應行操作的;語句級觸發器,是對應表操作的。

上面的區別,是在一條sql語句控制改變表時才會發生。
如果,通過insert一條一條的向表中插入數據,它們就都會觸發。

23.5.行級別觸發器的偽記錄變量

img29.png
行級觸發器的強大之處:可以獲取修改前后的值。

上面的表格很容易理解,只有update修改前后才都有值。
如果是insert插入,插入前是沒有值的(null).
如果是delete刪除,刪除后是沒有值的(null).

需求:使用觸發器,保證漲后的工資不能少于漲前的工資

分析:使用行級觸發器,能夠獲取修改前后的值進行比較,如果漲后工資還低的話,拋出例外(異常)

create or replace trigger tri_addsal  --創建觸發器
before update on emp  --定義觸發條件
for each row --定義行級觸發器
declare

begin
  if :new.sal<=:old.sal then
    raise_application_error(-20001,'漲后工資不能低于漲前的工資!?。?);
  end if;
end;

測試:
update emp set sal=sal-100;
img30.png

23.6. 觸發器的應用場景及注意事項

觸發器可應用于:

  • 數據確認
  • 實施復雜的安全性檢查
  • 做審計,跟蹤過表上所做的數據操作等
  • 數據的備份與同步

觸發器的注意事項:

觸發器會引起鎖,降低效率!使用時要慎重。如無必要,盡量不要使用觸發器

行級觸發器會引發行級鎖(鎖行數據)
語句級觸發器可能會引起表級鎖(鎖表)

存儲過程練習:

  1. 寫一個存儲過程,輸出久久乘法表

    首先可以先寫出java代碼,然后根據java代碼寫出PLSQL代碼
    public void test3() {
        for(int i=1;i<=9;i++) {
            for(int j=1;j<=i;j++) {
                System.out.print(j+"*"+i+"="+j*i+"  ");
            }
            System.out.println();
        }
    }

    九九乘法表的存儲過程:
    
    create or replace procedure proc_ninemul  --創建一個無參的存儲過程
    is
      --聲明兩個變量
      i number := 1;
      j number;
    begin
      --邏輯部分
      loop
        exit when i > 9;--退出外循環的條件
        j := 1; --每次執行內循環之前,將j設為1
        loop
          exit when j>i;--退出內循環的條件
          dbms_output.put(i||'*'||j||'='||i*j||'  ');--內循環共輸出一行
          j := j+1;
        end loop;
        --每次執行完內循環換行
        dbms_output.put_line('');
        i := i+1;
      end loop;
    end proc_ninemul;

    --調用
    begin
      proc_ninemul;
    end;
img31.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容