SqlPlus 編程點滴

pl/sql是oracle在標準的sql語言上的擴展。其不僅允許嵌入sql語言,還可以定義變量和常量,允許使用條件語句和循環語句,允許使用例外處理各種錯誤。

pl/sql可以在數據庫中寫入存儲過程(過程,函數,觸發器)。

  • 創建過程:create or replace procedure 過程名 is/as

begin
執行部分:
insert into 表名 (字段名) values(字段信息);
end;

create or replace procedure sbly_pro1 is
	begin
		insert into imagination values('sbly','world',20)
	end

查看錯誤: show error;

  • 調用該過程:
1.SQL> exec 過程名(參數1,參數2,……);
2.SQL> call 過程名(參數1,參數2,……); /*注意:exec調用過程時若過程沒有參數則可以不寫括號,而call必須寫括號,即使過程沒有參數!*/

編寫規範:

1.註釋規範單行註釋: --;
多行註釋: /*……*/;
2.標識符的命名規範
1.當定義變量時,建議用v_作爲前綴,如v_sal;
2.當定義常量時,建議用c_作爲前綴,如c_rate;
3.當定義遊標時,建議用_cursor作爲後綴,如emp_cursor;
4.當定義例外時,建議用e_作爲前綴,如e_error;

語法

pl/sql由3個部分構成,定義部分,執行部分,例外處理部分,如下:
declear
/*定義部分:定義常量,變量,遊標,例外,複雜數據類型*/
begin
/*執行部分:要執行的pl/sql語句和sql語句*/
exception
/*例外處理部分:處理運行的各種錯誤*/
end;

特別說明:

定義部分是從declear開始的
該部分是可選的
執行部分是從begin開始的
該部分是必需的
例外處理部分是從exception開始的
該部分是可選的

pl/sql分類:
過程:
過程用於執行特定的操作,當建立過程時,既可以指定輸入參數(in),也可以指定輸出參數(out),通過在過程中使用輸入參數,可以將數據傳遞到執行部分;通過使用輸出參數,可以將執行部分的數據傳遞到應用環境。在sqlplus中可以使用create procedure命令來建立過程。
實例:
1.編寫一個過程,可以輸入僱員名,新工資,可以修改僱員的工資
2.如何調用過程有兩種方法:exec 過程名/call 過程名

create procedure sbly_pro3(name varchar2,newsal number)--創建帶有參數的過程
	is
	begin--執行部分,根據用戶名修改工資;
	<span style="white-space:pre">	</span>update emp set sal=newsal where ename=name;
	end;
函數:
函數用於返回特定的數據,當建立函數時,在函數頭部必須包含return子句,而在函數體內必須包含return語句返回的數據。可以使用create function來建立函數
實際案例:
create function annual_income(name varchar2)
	return number is
	annual_salary number(7,2);
	begin select sal*12+nvl(comm,0) into annual_salary from emp where ename=name;
	return annual_salary;
end;

在sqlplus中調用函數:
SQL> var income number
SQL> call annual_income('SCOTT') into income;
SQL> print income

包:
包用於在邏輯上組合過程和函數,它由包規範和包體兩部分組成。
1.我們可以使用create package命令來創建包:
實例:

SQL> create package income_package
is
procedure update_sal(name varchar2,newsal number);
function annual_income(name varchar2) return number;
end;

包的規範只包含了過程和函數的說明,但是沒有過程和函數的實現代碼,包體用於實現包規範中的過程和函數。
2.建立包體可以用create package body命令
實例:
SQL> create package body income_package
is
purcedure update_sal(name varchar2,newsal number)
is
begin
update emp set sal=newsal where ename=name;
end;
function annual_income(name varchar2) return number
is
begin
select sal*12+nvl*12 into annual_income from emp where ename=name;
return annual_income;
end;
end;
調用包的過程或是函數:
當調用包的過程或是函數時,在過程和函數前需要帶有包名,如果要訪問其他方案的包,則還需要在包名前加方案名。
如:
SQL> call income_package.update_sal('SCOTT',1500);
特別說明:包在pl/sql中非常重要,在使用過程分頁時,將廣泛用到包並且體會到其強大

觸發器:
觸發器是指隱含執行的存儲過程,當定義觸發器時,必須要指定觸發的事件和觸發的操作,常用的觸發事件包括insert,update,delete.觸發器事件實際就是一個pl/sql塊,可以使用create trigger創建觸發器

定義並使用變量:
1.標準類型(scalar)
2.複合類型(composite)
3.參照類型(reference)
4.lob(large object)

標量(scalar)-常用類型
編寫時如果要使用變量,需要在定義部分定義變量。定義常量和變量的語法如下:
identifier [constant] datatype [not null] [:=| default expr]
identifier:名稱;constant:指定常量,需要指定它的初始值,且其值是不能改變的;datatype:數據類型;not null:指定變量值不能爲空;:=給變量或者是常量指定初始值;default:用於指定初始值;expr:指定初始值的pl/sql表達式,可是文本值、其他變量、函數等*
標量的定義案例:
1.定義一個變長字符串:v_ename varchar2(13);
2.定義一個小數,範圍在-9999.99~9999.99:v_sal number(6,2);
3.定義一個小數,並給予初值爲5.4:v_sal number(6.2):=5.4;
4.定義一個日期:v_hiredate date;
5.定義一個布爾變量,不能爲空切初值爲false:v_valid boolean not null default false;

舉例:下面以輸入員工工號,顯示僱員姓名,工資,個人所得稅(稅率爲0.03)爲例,說明變量的使用:

SQL> declare
--常量:稅率
c_tax_rate number(3.2):=0.03;
--變量:
v_ename varchar2(5);
v_sal number(7,2);
v_tax_sal number(7,2);
begin
select ename,sal into v_ename,v_sal from emp where empno=&no;
--計算所得稅
v_tax_sal:=v_sal*c_tax_rate;
--輸出:
dbms_output.put_line('姓名是:'||v_ename||'工資是:'||v_sal||'交稅:'||v_tax_sal);
end;

標量(scalar)-使用%type類型
--/*對於上面的塊來說:如果員工姓名超過了規定字符數的話就會有錯誤,爲了降低pl/sql程序的維護工作量,可以使用%type屬性定義變量,這樣它會按照數據庫列來確定你定義的變量的類型和長度*/
舉例:

SQL> 標識符名 表名.列名%type;
如上面:
v_ename emp.ename%type;
v_sal emp.sal%type;
複合變量(composite)-介紹:
用於存放多個值的變量,主要包括這幾種:
1.pl/sql記錄
2.pl/sql表
3.嵌套表
4.varray
複合類型-pl/sql記錄:
pl/sql記錄類似於高級語言中的結構體,注意:當引用pl/sql記錄成員時,必須要加上記錄變量作爲前綴(記錄變量.記錄成員)。如下:

SQL> declare
type emp_record_type is record(
name emp.ename%type,
salary emp.sal%type,
title emp.job%type);--以上幾行爲定義一個emp_record_type類型,其中可以存放name,salary,title三個變量。類似於結構體
myrecord emp_record_type;--定義myrecord變量爲emp_record_type類型
begin
select ename,sal,job,into myrecord from emp where empno=7788;--查找表中empno爲7788的員工的信息存入myrecord中
dbms_output.put_line('員工名:'||myrecord.name||'薪水:'||myrecord.salary);--從myrecord中取出得到的值進行打印
end;
複合類型-pl/sql表:
pl/sql表類似於高級語言中的數組,但是需要注意:高級語言中數組下標不能爲負數,而pl/sql表可以爲負數,並且表元素下個書沒有限制。如下:
SQL> declare
/*定義了一個pl/sql表類型,名字是my_table_type。該類型是用於存放emp.ename%type類型的元素(數組功能)*/
--index by binary_integer:表示下標是整數(包括正整數,負整數,0)
type my_table_type is table of emp.ename%type index by binary_integer;
my_table my_table_type;--定義了一個my_table變量,類型爲my_table_type;
begin
select ename into my_table(0) from emp where empno=7788;--將工號是7788的員工姓名放到my_table變量的0號位置上
dbms_output.put_line('員工名:'||my_table(0));--打印出0號位置的值
end;
--注意:若將select語句中的where子句去掉,則會報錯,報"實際返回的行數超出請求行數",既對應數組一個位置只能放置一個值。

參照變量:
用於存放數值指針的變量,通過使用參照變量,可以是的應用程序共享相同的對象,從而降低佔用空間。在編寫pl/sql程序時,可以使用遊標變量(ref cursor)和對象類型的變量(ref obj_type)兩種參照變量
參照變量-ref cursor遊標變量
使用遊標時,當定義遊標時不需要指定相應的select語句,但是當使用遊標時(open時)需要指定select語句,這樣一個遊標就與一個select語句結合了。如下:
1.請使用pl/sql編寫一個塊,可以輸入部門好,並顯示該部門所有員工姓名和工資

SQL> declare
--定義
--定義一個遊標
type my_emp_cursor is ref cursor;
--定義一個遊標變量
test_cursor my_emp_cursor
--定義變量
v_name emp.ename%type;
v_sal emp.sal%type;
--執行
begin
--把test_cursor和一個select結合
open test_cursor for select ename,sal from emp where deptno=&no;
--循環取出
loop
fetch test_cursor into v_name,v_sal;
--判斷是否退出,既遊標test_cursor爲空
exit when test_cursor%notfound;
dbms_output.put_line('名字:'||v_name||'工資'||v_sal);
end loop;
end;

控制結構:
1.使用if語句
2.使用循環語句
3.使用控制語句——goto,null;
條件分支:
1.if——then
2.if——then——else
3.if——then——elsif——else
案例:
1.if——then語句
編寫一個過程,可以輸入一個僱員名,如果該僱員的工資低於2000,就給該僱員工資增加%10;

SQL> create or replace procedure sbly_pro4(name varchar2) is
--定義
v_sal emp.sal%type
begin
select sal into v_sal from emp where ename=name;
--判斷
if v_sal<2000
then
update emp set sal=sal+sal*0.1;
end if;
end;
2.二重分支條件  if——then——else
編寫一個過程,可以輸入一個僱員名,如果該僱員的補助不是0則增加10%,如果是0就將補助改爲200
SQL> create or replace procedure sbly_pro5(name varchar2) is
--定義
v_comm emp.comm%type;
begin
select sal into v_comm from emp where ename=name;
if v_comm=0
then
update emp set comm=200 where ename=name;
else
update emp set comm=comm+comm*0.1 where ename=name;
end if;
end;
3.多重分支條件 if——then——elsif——then——else
編寫一個過程,可以輸入一個僱員編號,如果該僱員的職位是'king'則工資上漲13,如果是該僱員的職位是'queen'則工資上漲130,其他人的工資漲1300
SQL> create or replace procedure sbly_pro6(num number)
is
v_job emp.job.%type;
begin
select job into v_job from emp where empno=num;
if
v_job='king'
then
update emp set sal=sal+13 where empno=num;
elsif
v_job='queen'
then
update emp set sal=sal+130 where empno=num;
else
update emp set sal=sal+1300 where empno=num;
end if;
end;
循環語句-loop
loop是pl/sql最簡單的循環語句,以loop開頭,以end loop結尾。
/*注意:這種循環至少被執行一次*/
案例:現有一張users表,表結構:
用戶id   |  用戶名
編寫一個過程,可規定用戶名,並循環添加10個人到user表中,用戶編號從1開始
SQL> create or replace procedure sbly_pro7(name varchar2)
is
--定義:
v_num number:=1;
begin
loop
insert into users values(v_num,name);
--判斷退出循環條件
exit when v_num=10;
--自增:
v_num=v_num+1;
end loop;
end;
循環語句-while循環
只有條件爲true纔會執行循環體語句,既:可能一次都不循環
以while...loop開始,以end loop結束
案例:
用上一個案例的表結構
編寫一個過程,可規定用戶名,並循環添加10個人到user表中,用戶編號從11開始
SQL> create or replace procedure sbly_pro7(name varchar2)
is
v_num:=11;
begin
while v_num<=20
loop
insert into users values(v_num,name);
v_num=v_num+1;
end loop;
end;

循環語句-for 循環
基本for循環的基本結構如下:
begin
for i in reverse 1..10 loop
insert into users values(i,'字符串');
end loop;
end;
順序控制語句:-goto null
1.goto語句用於跳轉到指定的標號去執行語句
/*注:使用goto會增加程序的複雜性,使程序可讀性變差。一般情況下不建議使用*/
基本語法如下:goto label;其中lable是已經定義好的標號
SQL> 
declare
i int:=1;
begin
loop
dbms_output.put_line('輸出i='||i);
if i=10
then
goto end_loop;
end if;
i:=i+1;
end loop;
<<end_loop>>
dbms_output.put_line('循環結束');
end;
2.null
null語句不會執行任何操作,且會直接將控制傳遞到下一條語句。使用的好處是可以提高程序可讀性
舉例:
SQL> declare
v_sal emp.sal%type;
v_name emp.ename%type;
begin
select ename sal into v_name v_sal from emp where ename=&no;
if v_sal<3000
then
update emp set comm=sal*0.1 where ename=&no;
else
null;
end if;
end;

編寫一個過程,可以向book表添加書,要求通過java程序調用該過程
--創建表:
SQL> create table book(bookId number,bookName varchar2(52),publishHouse varchar2(52));
--編寫過程

SQL> create or replace procedure sbly_pro8(ID in number,name in varchar2,publish in varchar2)
--in表示是一個輸入變量
/*注意:如果不寫in則默認是in.此處是因爲之後有輸出變量*/
--out表示是一個輸出變量\
is
begin
insert into book values(ID,name,publish);
end;

有返回值的存儲過程(非列表)
案例:
編寫一個過程,可以輸入僱員的編號,返回僱員姓名。
SQL> create or replace procedure sbly_pro9(num in number,name out varchar2)
is
begin
select ename into name from emp where empno=num;
end;
//注意:基本案例已被一下擴展案例替代,若要改回請重新執行上面代碼
案例擴張:編寫一個過程,可以輸入僱員的編號,返回該僱員的姓名工資和崗位。
SQL> create or replace procedure sbly_pro9(num in number,name out varchar2,salary out number,empjob out varchar2)
is
begin
select ename,sal,job into name,salary,empjob from emp where empno=num;
end;
/*注意:name是一個輸出變量*/
有返回值的存儲過程(列表[結果集])
案例:
編寫一個過程,輸入部門號,返回該部門所有僱員信息。 對該題分析如下:
由於oracle存儲過程沒有返回值,它的所有返回值都是通過out參數來代替的,但是因爲列表是集合,所以不能用一般的參數,必須用到package,所以要分2部分:
1.建立一個包,如下:
create or replace package testpackage
AS
TYPE test_cursor is ref cursor;
end testpackage;

2.建立一個存儲過程,如下:
create or replace procedure sbly_pro10(num in number,cur out testpackage.test_cursor)
is
begin
open cur for select * from emp where deptno=num;
end;
Oracle 的分頁:
舉例:從emp表中查找第3,4個信息:
SQL> select * from (select a1.*,rownum rn from (select * from emp) a1 where rownum<=4) where rn>=3;
/*注意:此處語句可作爲一個模板使用。解釋如下:*/
/*解釋:先查找出emp表中所有信息,將此表取別名:a1;繼而通過自嵌套查詢a1中所有信息,並且將分頁信息rownum顯示出來,末尾條件判斷爲:小於(等於)某一分頁的所有內容;將以上查詢結果再次看作一張整體表,查找其全部信息,並限定分頁別名爲大於(等於)某一分頁的全部記錄……到此,分頁查詢完畢*/
編寫分頁需要用到的包:
SQL> create or replace package sbly_package2
as
type page_cursor is ref cursor;
end sbly_package2;
案例
SQL> create or replace
procedure sbly_pro11
(
tablename varchar2,--表名
pagesize number,--每頁顯示的記錄數
pagenow number,--顯示的頁碼
myrows out number,--總記錄數
pagecount out number,--總頁數
p_cursor out sbly_package2.page_cursor--返回的記錄集
)
is
v_sql varchar2(1001);
--定義兩個整數
/*注意:以下兩個變量爲分頁算法!切記*/
/*注:分頁算法在該課程第12講有涉及,且在servlet第4講中有講解*/
v_begin number:=(pagenow-1)*pagesize+1;
v_end number:=pagenow*pagesize;
begin
v_sql:='select * from (select a1.*,rownum rn from (select * from '||tablename||') a1 where rownum<='||v_end||') where rn>'||v_begin;
--把遊標和sql語句關聯起來
open p_cursor for v_sql;
--計算rows和pagecount
--組織一個sql語句
v_sql:='select count(*) from '||tablename;
--執行sql語句並把返回值賦給rows
execute immediate v_sql into myrows;
--計算pagecount
if
--注意:pl/sql中沒有'%'取模的寫法,既:rows%pagecount爲錯誤語句。此處需要用mod函數
mod(myrows,pagesize)=0
then
pagecount:=myrows/pagesize;
else
pagecount:=myrows/pagesize+1;
end if;
--close p_cursor;
end;

例外處理:
分爲預定義例外,非預定義例外和自定義例外
預定義例外:用於處理常見oracle錯誤
非預定義例外:用於處理預定義例外不能處理的例外
自定義例外:用於處理與oracle錯誤無關的其他情況

例外傳遞:
案例:編寫一個過程,接受僱員的編號,先是僱員姓名
declare
v_name emp.ename%type
begin
select ename into v_name from emp where empno=&no;
dbms_output.put_line('該員工是:'||v_name);
--例外處理
exception
when no_data_found
then
dbms_output.put_line('未找到數據');
end;
/
問題:輸入僱員編號不存在時會出現例外
/*注:已在程序中處理*/


處理預定義例外:
預定義例外是由pl/sql提供的系統例外。當pl/sql應用程序違反了oracle規定的限制時,會隱含出發一個內部例外。pl/sql提供了二十多個預定義例外
case_not_found:在編寫case語句時如果在when子句中沒有包含必須的條件分支就會觸發該例外
案例:
SQL> create or replace procedure test_pro(num number)
is
v_sal emp.sal%type;
begin
select sal into v_sal where empno=num;
case
when
v_sal<1000
then
update emp set sal=sal+100 where empno=num;
when
then
v_sal<2000
update emp set sal=sal+200 where empno=num;
end case;
exception
when
case_not_found
then
dbms_output.put_line('case語句沒有與'||v_sal||'相匹配的條件');
end;
/
//問題:如果v_sal>2000則不會進入任何一個分支,此時出發該例外


預定義例外
cursor_already_open:當重新打開已經打開的遊標時,會隱含地觸發該例外
案例:
SQL> declare
cursor emp_cursor is select ename,sal from emp;
begin
open emmp_cursor;
for emp_record1 in emp_cursor
loop
dbms_output.put_line(emp_record1.ename);
end loop;
exception
when
cursor_already_open
then
dbms_output.put_line('遊標已打開');
end;
/*注意:此處open語句已經打開遊標,而for emp_record1 in emp_cursor又打開一次遊標,則會觸發此異常*/

預定義例外
dup_val_on_index:在唯一索引所對應的列表上插入重複的值,會隱含觸發該例外
案例:
begin
insert into dept values(10,'公關部','北京');
exception
when
dup_val_on_index
then
dbms_output.put_line('在deptno列上不能出現重複值');
end;

/*注意:假設此處10號部門已經存在且不能重複*/

預定義例外
invaild_cursor:當試圖在不合法的遊標上執行操作時(無該遊標存在或者遊標未打開之類稱爲非法遊標),會觸發該例外
例如:視圖從沒有打開的遊標提取數據,或是關閉沒有打開的遊標
SQL> declare
cursor emp_cursor is select ename,sal from emp;
emp_record emp_cursor%rowtype;
begin
open emp_cursor;--打開遊標被註釋
fetch emp_cursor into emp_record;
dbms_output.put_line(enp_record.ename);
close emp_cursor;
exception
when
invaild_cursor
then
dbms_output.put_line('遊標未打開');
end;

/*注:將開啓遊標一句註釋之後,查詢遊標中的值與關閉遊標就爲非法操作*/


預定義例外:
invalid_number:當輸入的數據有錯誤則會觸發該例外
案例:
begin
update emp set sal=sal+'2b';
exception
when
invalid_number
then
dbms_output.put_line('輸入數據有誤');
end;

預定義例外:
no_data_found:之前使用過,不再作解釋與演示
預定義例外:
too_many_rows:當執行select into 語句時,如果返回值超過一行,則會觸發該例外
案例:
SQL> declare
v_ename emp.ename%type;
begin
select ename into v_ename from emp;
exception
when
too_many_rows;
then
dbms_output.put_line('返回多個值');
end;
/*注意:v_ename只能接受1個值*/
預定義例外:
zero_divide:除以0的例外

預定義例外:
value_error:當執行賦值操作時,如果變量的長度不足以容納實際數據,則觸發該例外
案例:
SQL> declare
v_ename varchar2(5);
begin
select ename into v_ename from emp where empno=&no;
dbms_output.put_line(v_ename);
exception
when
value_error
then
dbms_output.put_line('超出變量尺寸');
end;
/*注意:當輸入工號比對後返回的姓名大於5個字符則會引發異常*/
其他預定義例外:
login_denide:當用戶非法登陸時,會觸發該例外
not_logged_on:如果用戶沒有登陸就執行dml操作,就會觸發該例外
storage_error:如果超出了內存空間或者內存被損壞就會觸發該例外
timeout_on_resource:如果oracle在等待資源時出現超時就會觸發該例外

非預定義例外:
用於處理與預定義例外無關的oracle錯誤。比如pl/sql塊中執行dml語句時違反約束規定等等,在這樣的情況下也可以處理各種例外
略
處理自定義例外:
預定義例外和非預定義例外都與oracle錯誤相關,並且出現的oracle錯誤會隱含的處罰相關例外。而自定義例外與oracle錯誤沒有任何關聯,它是由開發人員爲特定的情況所定義的例外
案例:
編寫一個pl/sql塊,接受一個僱員的編號,並給該僱員工資增加130.若該僱員不存在則提示
/*注意:當語句爲update語句時系統不會報no_data_found例外。若此時確實要認定例外則使用自定義例外*/
SQL> create or replace procedure sbly_pro12(num number)
is
--定義一個例外:
sblyex1 exception;
begin
update emp sat sal=sal+130 where empno=num;
if
sql%notfound--該語句表示沒有更新操作
then
raise sblyex1;--該語句表示觸發該例外
end if;
exception
when
sblyex1
then
dbms_output.put_line('沒有更新,因爲沒有該僱員');
end;
視圖:
試圖使一個虛擬表,內容有查詢定義。同真實的表一樣,試圖包含一系列帶有名稱的列和數據。但是視圖並不在數據庫中以存儲的數據值集形式存在。行和列數據來自由定義視圖的查詢所引用的表,並且在引用視圖時動態生成


表和視圖的區別:
1.表需要佔用磁盤空間,視圖不需要
2.視圖不能添加索引
3.使用試圖可以簡化複雜查詢
4.視圖利於提高安全性
創建視圖:
SQL> create view 視圖名 as select語句[with read only];
案例:創建一個視圖顯示僱員的編號,姓名,部門名稱 (多表)
SQL> create view sblyv1 as select emp.empno,emp.ename,dept.dname from emp,dept where emp.deptno=dept.deptno;

創建或修改視圖:
SQL> create or replace view 視圖名 as select語句 [with read only];

刪除視圖:
drop view 視圖名;
//注意:多張試圖可以再次作爲多表進行多表查詢進而再次降低複雜度
//注意:末尾的with read only表示視圖只是可讀而不可改

注:本文學習自韓順平老師的Oracle視頻(ITCAST)

發佈了46 篇原創文章 · 獲贊 5 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章