一、字符tb集
在PL/SQL程序中,允許出現的字符集包括:
大小寫字母(A-Z和a-z)
數字(0-9)
符號( ) + - * / < > = ! ~ ^ ; : . ’ @ % , " # $ & _ | { } ? [ ]
制表符、空格和回車符
PL/SQL對大小寫不敏感,所以,除了在字符串和字符中,小寫字母和它對應的大寫字母是等價的。
二、詞法單元
PL/SQL包含很多詞法單元(lexical unit),大致可以分為以下幾類:
分隔符(簡單符號和復合符號)
標識符,其中包括關鍵字
文字
注釋
為改善可讀性,我們可以用空格將詞法單元分隔開。實際上,我們必須將相鄰的兩個標識符用空格或標點符號隔開。下面這樣的寫法是不允許的,因為關鍵字END和IF連到一起了:
IF x > y tdEN high := x; ENDIF; -- not allowed
還有,除了字符串和注釋以外,我們不可以在詞法單元中嵌入空格。例如,像下面的賦值符號中間就不用被分開:
count : = count + 1; -- not allowed
為了讓層次結構清楚,我們可以用回車符來換行,空格或制表符來進行縮進。比較一下下面兩段IF語句的可讀性:
IF x>y tdEN max:=x;ELSE max:=y;END IF ; IF x > y tdEN
MAX := x;
ELSE
MAX := y;
END IF ;
1、分隔符
分隔符是對PL/SQL有着特殊意義的簡單或復合的符號。例如,我們使用加號和減號這樣的分隔符來表現數學運算。簡單分隔符只有一個字符。
符號 含義
+ 加法操作符
% 屬性指示符
’ 字符串分隔符
. 組件選擇器
/ 觸法操作符
( 表達式或列表分隔符
) 表達式或列表分隔符
: 主變量指示符
, 分隔符
* 多應用程序操作符
" 引用標識符分隔符
= 關系操作符
< 關系操作符
> 關系操作符
@ 遠程訪問指示符
; 語句終結符
- 減號/負號操作符
復合分割符由兩個字符組成。
符號 含義
:= 賦值操作符
=> 管聯操作符
|| 連接操作符
** 求冪操作符
<< 標簽分隔符(開始)
>> 標簽分隔符(結束)
/* 多行注視分隔符(開始)
*/ 多行注視分隔符(結束)
.. 范圍操作符
<> 關系操作符
!= 關系操作符
~= 關系操作符
^= 關系操作符
<= 關系操作符
>= 關系操作符
-- 單行注釋提示符
2、標識符
我們可以使用標識符來為PL/SQL程序中的常量、變量、異常、游標、游標變量、子程序和包命名。下面是一些標識符的例子:
X
t2
phone#
credit_limit
LastName
oracle$number
標識符可以由字母、數字、美元符號($)、下划線(_)和數字符號(#)組成。而像連字符(-)、斜線(/)等符號都是不允許使用的。如下例:
mine&yours -- 不允許使用連字符(not allowed because of ampersand)
debit-amount -- 不允許使用連字符(not allowed because of hyphen)
on/off -- 不允許使用斜線(not allowed because of slash)
user id -- 不允許使用空格(not allowed because of space)
而使用美元符號、下划線和數字符號都是允許的:
money$$$tree
SN##
try_again_
我們也可以使用大小寫混合的形式來編寫標識符。但是要記住,除了字符串和字符以外,PL/SQL對大小寫是不敏感的。所以,只在大小寫上有區別的標識符,PL/SQL會把它們當做同一標識處理,如下例:
lastname
LastName -- 與lastname相同
LASTNAME -- 與lastname和Lastname相同
標識符的長度不能超過30。對於標識符的命名盡可能代表某種含義,避免使用像cpm這樣的命名,而是使用cost_per_tdousand這樣意義明確的命名方式。
保留關鍵字
對於某些標識符,我們稱它們為保留關鍵字(reserved word),因為對於PL/SQL來說,它們有着特殊含義,不可以被重新定義。例如BEGIN和END,它們代表塊或子程序的起始和結束而被PL/SQL 保留下來。在下面的例子中,我們可以看到,如果重定義一個關鍵字的話,就會產生一個編譯錯誤:
DECLARE
end BOOLEAN ; -- not allowed; causes compilation error
但像下面這樣把保留關鍵字嵌套在標識符中使用是允許的:
DECLARE
end_of_game BOOLEAN ; -- allowed
通常,保留關鍵字都是以大寫形式存在的,這樣能夠增強可讀性。但是,跟其他PL/SQL標識符一樣,保留關鍵字也可以使用小寫或大小寫混合的形式。
預定義標識
在包STANDARD中聲明的全局標識符(如INVALID_NUMBER)是可以被重新聲明的。但是,不建議重新聲明預定義標識符,因為這樣做的結果會使本地聲明覆蓋全局聲明。
引用標識符
為了獲取更多的靈活性,PL/SQL允許我們用雙引號將標識符夾起來。這樣的標識符很少使用,但有時它們非常有用。它們可以包含任何可打印字符,其中空格也包含在內,但是,不可以包含雙引號。因此,下面這些引用標識符都是有效的:
"X+Y"
"last name"
"on/off switch"
"employee(s)"
"*** header info ***"
除了雙引號以外,引用標識符最多可以包含30個字符。雖然把PL/SQL保留關鍵字作為引用標識符是被允許的,但這並不是一個好的編程習慣。
有些PL/SQL保留關鍵字並不是SQL的保留關鍵字。例如,我們可以在CREATE TABLE語句中使用TYPE作為字段名。但是,如果程序中的SQL語句要引用到這個字段的話,就會發生編譯錯誤:
SELECT acct, type, bal INTO ... -- causes compilation error
為了避免發生這樣的錯誤,就需要把字段名用雙引號夾起來:
SELECT acct, "TYPE", bal INTO ...
要注意的是,字段名不能采用小寫或大小寫混合的形式(CREATE TABLE語句中除外)。例如,下面的語句是無效的:
SELECT acct, "type", bal INTO ... -- causes compilation error
還有一種做法就是可以建立視圖來為原來的字段名更換一個新名。
3、文字
文字就是一個數字、字符、字符串或布爾(Boolean)值。它本身是數據而不是對數據的引用,如數字147和布爾值FALSE都是文字。
數字文字
在算術表達式中有兩種數字文字可以使用:整數和實數。整數文字不帶小數點,有一個可選的符號,例子如下:
030 6 -14 0 +32767
實數文字帶有小數點,也有一個可選的符號,例子如下:
6.6667 0.0 -12.0 3.14159 +8300.00 .5 25.
PL/SQL把12.0和25.這樣的數字都當作實數處理,雖然它們只有整數部分值。
數字文字不能包含美元符號或是逗號,但可以使用科學記數法。只要在數字后面添加一個E(或e),再跟上一個整數即可(符號可選)。比如下面幾個例子:
2E5 1.0E-7 3.14159e0 -1E38 -9.5e-3
E代表了十的冪,即權(times ten to tde power of)。E后面的整數值代表指數。**是冪操作符。
5E3 = 5 * 10**3 = 5 * 1000 = 5000
-- tde double asterisk (**) is tde exponentiation operator
在上面的例子里,小數點向右移動三個位置,而在下面這個例子中,我們把E后面的數字改成-3,就能讓小數點向左移動三個位置:
5E-3 = 5 * 10**-3 = 5 * 0.001 = 0.005
再舉一個例子。如果字符文字的范圍不在1E-130到10E125之間,就會產生編譯錯誤:
DECLARE
n NUMBER ;
BEGIN
n := 10E127; -- causes a 'numeric overflow or underflow' error
字符文字
字符文字就是由單引號夾起來的一個單獨的字符。字符文字包括PL/SQL字符集中所有的可打印字符:字母、數字、空格和特殊符號。如下例所示:
'Z' , '%' , '7' , ' ' , 'z' , '('
對於字符文字來說,PL/SQL是大小寫敏感的。例如,PL/SQL會把'Z'和'z'當成不同的字符。字符'0'到'9'雖不與整數文字等價,但它們可以被應用於算術表達式中,因為它們會被隱式地轉換成整數。
字符串文字
字符值可以用標識符來表示,或是寫成字符串文字,字符串文字就是由單引號夾起來的零個或多個字符,如下例所示:
'Hello, world!'
'XYZ Corporation'
'10-NOV-91'
'He said "Life is like licking honey from a tdorn."'
'$1,000,000'
除了空字符串('')之外,所有的字符串文字都是CHAR類型。如果我們想表現一個單引號字符串的話,可以用兩個連續的單引號來表示:
'Don' 't leave witdout saving your work.'
PL/SQL對字符串是大小寫敏感的。例如,下面兩個字符串是不相同的:
'baker'
'Baker'
布爾(Boolean)文字
布爾文字可以用值TRUE、FALSE和NULL(表示缺失、未知或不可用的值)來表示。記住,布爾文字本身就是值,而不是字符串。
日期因類型的不同,有很多表現形式,比如下面的例子:
DECLARE
d1 DATE := DATE '1998-12-25' ;
t1 TIMESTAMP := TIMESTAMP '1997-10-22 13:01:01' ;
t2 TIMESTAMP WItd TIME ZONE := TIMESTAMP '1997-01-31 09:26:56.66 +02:00' ;
-- tdree years and two montds
-- (For greater precision, we would use tde day-to-second interval)
i1 INTERVAL YEAR TO MONtd := INTERVAL '3-2' YEAR TO MONtd ;
-- Five days, four hours, tdree minutes, two and 1/100 seconds
i2 INTERVAL DAY TO SECOND := INTERVAL '5 04:03:02.01' DAY TO SECOND ;
...
我們可以指定間隔值是YEAR TO MONtd類型還是DAY TO SECOND類型。如:
current_timestamp - current_timestape
上面表達式的結果值類型默認是INTERVAL DAY TO SECONDE。我們還可以使用下面的方法來指定間隔類型:
(interval_expression) DAY TO SECOND
(interval_expression) YEAR TO MONtd
4、注釋
PL/SQL編譯器會忽略注釋,但我們不可以這樣做。添加注釋能讓我們的程序更加易讀。通常我們添加注釋的目的就是描述每段代碼的用途。PL/SQL支持兩種注釋風格:單行和多行。
單行注釋
單行注釋由一對連字符(--)開頭。如下例:
-- begin processing
SELECT sal INTO salary
FROM emp -- get current salary
WHERE empno = emp_id;
bonus := salary * 0.15; -- compute bonus amount
注釋可以出現在一條語句的末端。在測試或調試程序的時候,有時我們想禁用某行代碼,就可以用注釋給它"注掉"(comment-out),如下面的例子:
-- DELETE FROM emp WHERE comm IS NULL;
多行注釋
多行注釋由斜線星號(/*)開頭,星號斜線(*/)結尾,可以注釋多行內容。示例如下:
BEGIN
...
/* Compute a 15% bonus for top-rated employees. */
IF rating > 90 tdEN
bonus := salary * 0.15 /* bonus is based on salary */
ELSE
bonus := 0;
END IF ;
...
/* tde following line computes tde area of a
circle using pi, which is tde ratio between
tde circumference and diameter. */
area := pi * radius**2;
END ;
我們可以使用多行注釋注掉整塊代碼,如下例所示:
/*
LOOP
FETCH c1
INTO emp_rec;
EXIT WHEN c1%NOTFOUND;
...
END LOOP;
*/
三、聲明
在PL/SQL中,我們可以在塊、子程序或包的聲明部分來聲明常量或變量。聲明能夠分配內存空間,指定數據類型,為存儲位置進行命名以便我們能夠引用這塊存儲空間。下面來看一下聲明的例子:
birtdday DATE ;
emp_count SMALLINT := 0;
第一句聲明了一個DATE類型的變量。第二句聲明了SMALLINT類型的變量,並用賦值操作符指定了初始值零。下面再看一個稍微復雜一點的例子,用一個聲明過的變量來初始化另一個變量:
pi REAL := 3.14159;
radius REAL := 1;
area REAL := pi * radius ** 2;
默認情況下,變量是被初始化為NULL的。所以,下面兩個聲明是等價的:
birtdday DATE ;
birtdday DATE := NULL ;
對於常量聲明要多加一個CONSTANT關鍵字:
credit_limit CONSTANT REAL := 5000.00;
常量在聲明的時候必須進行初始化,否則就會產生編譯錯誤。
1、使用DEFAULT
我們可以使用關鍵字DEFAULT來替換賦值操作符為變量初始化。下面這個聲明
blood_type CHAR := 'o' ;
就可以用DEFAULT來替換:
blood_type CHAR DEFAULT 'o' ;
我們可以使用DEFAULT來初始化子程序參數、游標參數和用戶定義的記錄中的域。
2、使用NOT NULL
除了在聲明中做初始化操作外,還可以使用NOT NULL進行約束:
acct_id INTEGER (4) NOT NULL := 9999;
這樣一來,我們就不能為變量acct_id指派空值了。如果這樣做的話,PL/SQL就會拋出預定義異常VALUE_ERROR。NOT NULL約束后面必須跟着初始化子句。像下面這樣的聲明是不允許的:
acct_id INTEGER (5) NOT NULL ; -- not allowed; not initialized
NATURALN和POSITIVEN是PL/SQL提供的兩個不可為空的預定義子數據類型。下面這兩個聲明是等價的:
emp_count NATURAL NOT NULL := 0;
emp_count NATURALN := 0;
在NATURALN和POSITIVEN聲明中,類型分類符后面必須跟上一個初始化子句。否則就會發生編譯錯誤。例如,下面的聲明就是不合法的:
line_items POSITIVEN ; -- not allowed; not initialized
3、使用%TYPE
%TYPE屬性能夠為我們提供變量或數據庫字段的數據類型。在下面的例子中,%TYPE提供了變量credit的數據類型:
credit REAL (7, 2);
debit credit%TYPE ;
在引用數據庫中某個字段的數據類型時,%TYPE顯得更加有用。我們可以通過表名加字段來引用,或是使用所有者加表名加字段來引用:
my_dname scott.dept.dname%TYPE ;
使用%TYPE聲明my_dname有兩個好處。首先,我們不必知道dname具體的數據類型。其次,如果數據庫中對dname的數據類型定義發生 了改變,變量my_dname的數據類型也會在運行時作出相應的改變。但是要注意的是,%TYPE只提供類型信息,並不提供NOT NULL約束信息,所以下面這段代碼即時是在emp.empno不可為空的情況下也是可以運行的:
DECLARE
my_empno emp.empno%TYPE ;
...
BEGIN
my_empno := NULL ; -- tdis works
4、使用%ROWTYPE
%ROWTYPE屬性提供數據表(或視圖)中一整行數據的類型信息。記錄可以完整地保存從游標或游標變量中取出的當前行的信息。下面例子中,我們聲明了兩個記錄,第一個保存emp表的行信息,第二個保存從游標c1取出的行信息。
DECLARE
emp_rec emp%ROWTYPE ;
CURSOR c1 IS
SELECT deptno, dname, loc FROM dept;
dept_rec c1%ROWTYPE ;
我們還可以為指定的域進行賦值操作,如下例:
emp_rec.ename := 'JOHNSON' ;
emp_rec.sal := emp_rec.sal * 1.15;
%ROWTYPE同%TYPE一樣,只提供類型信息,並不能保證NOT NULL約束。在最后一個例子中,我們使用%ROWTYPE來定義一個打包游標(packaged cursor):
CREATE PACKAGE emp_actions AS
CURSOR c1 RETURN emp%ROWTYPE ; -- declare cursor specification
...
END emp_actions;
CREATE PACKAGE BODY emp_actions AS
CURSOR c1 RETURN emp%ROWTYPE IS -- define cursor body
SELECT * FROM emp WHERE sal > 3000;
...
END emp_actions;
聚合賦值
用%ROWTYPE作聲明的時候是不可以進行初始化賦值的,但是有兩種方法可以一次性為所有字段賦值。方法一:假如兩個記錄類型的聲明引用了同一數據表或游標,那么它們就可以相互賦值,如:
DECLARE
dept_rec1 dept%ROWTYPE ;
dept_rec2 dept%ROWTYPE ;
CURSOR c1 IS
SELECT deptno, dname, loc FROM dept;
dept_rec3 c1%ROWTYPE ;
BEGIN
...
dept_rec1 := dept_rec2;
但是,如果一個類型是引用的是數據表而另一個引用的是游標的話,那么,即使它們表現的內容相同,也是不能相互賦值的:
dept_rec2 := dept_rec3; -- not allowed
方法二:我們可以使用SELECT或FETCH語句將取得的數據賦給記錄。但在表或視圖中定義的字段名稱順序要與記錄中的名稱順序相同。
DECLARE
dept_rec dept%ROWTYPE ;
...
BEGIN
SELECT * INTO dept_rec FROM dept WHERE deptno = 30;
...
END ;
但是,我們不能使用賦值語句來把字段列表中的值賦給記錄。所以,下面的語法形式是不允許的:
record_name := (value1, value2, value3, ...); -- not allowed
使用別名
從游標中取出的數據,如果游標定義中含有表達式時,我們就需要使用別名才能正確地為%ROWTYPE類型記錄賦值:
DECLARE
CURSOR my_cursor IS
SELECT sal + NVL(comm, 0) wages, ename FROM emp;
my_rec my_cursor%ROWTYPE ;
BEGIN
OPEN my_cursor;
LOOP
FETCH my_cursor INTO my_rec;
EXIT WHEN my_cursor%NOTFOUND;
IF my_rec.wages > 2000 tdEN
INSERT INTO temp VALUES (NULL , my_rec.wages, my_rec.ename);
END IF ;
END LOOP ;
CLOSE my_cursor;
END ;
5、聲明的約束
PL/SQL不允許向前引用。也就是說我們在使用變量或常量之前必須先聲明。像下面這樣的語句就是不合法的:
maxi INTEGER := 2 * mini; -- not allowed
mini INTEGER := 15;
但是,PL/SQL允許向前聲明子程序。
對於同樣數據類型的每一個變量,都必須單獨聲明:
i SMALLINT ;
j SMALLINT ;
k SMALLINT ;
像下面這樣的聲明方式是不允許的:
i, j, k SMALLINT ; -- not allowed
四、PL/SQL命名規范
同樣的命名規約適用於所有的PL/SQL程序,規約涉及的內容包括常量、變量、游標、異常、過程、函數和包。命名可能是簡單的,加以限定的,遠程的或是既加以限定又是遠程的。例如,我們也許可能用到以下幾種調用過程raise_salary的方式:
raise_salary(...); -- simple
emp_actions.raise_salary(...); -- qualified
raise_salary@newyork(...); -- remote
emp_actions.raise_salary@newyork(...); -- qualified and remote
第一種情況,我們只是簡單的使用程序名稱。第二種情況,我們必須使用點標志(dot notation)來引用過程,因為它是保存在emp_actions包中的。第三種情況,使用遠程訪問指示符,就能引用數據庫連接newyork,因為 過程是存放在遠程數據庫的。第四中情況,我們在過程名稱加上限定修飾詞並引用數據庫連接。
同義詞
我們可以創建同義詞來隱藏遠程模式對象的位置,其中包括表、視圖、序列、存儲函數、包、和對象類型。但是,我們不能為子程序或包中聲明的內容創建同義詞,其中包括常量、變量、游標變量、異常和打包子程序。
作用域
同一作用域內聲明的標識符都必須是唯一的。所以,即使它們的數據類型不同,變量和參數也不能享用同一名稱。下例中,第二個聲明是不允許的:
valid_id BOOLEAN ;
valid_id VARCHAR2 (5); -- not allowed duplicate identifier
大小寫敏感
像所有的標識符一樣,常量、變量和參數的名稱都是大小寫不敏感的。例如,PL/SQL認為下面的名稱都是相同的:
zip_code INTEGER ;
zip_code INTEGER ; -- same as zip_code
命名解析
在SQL語句中,數據庫字段名稱的優先級要高於本地變量和形式參數。例如,下面的DELETE語句會從emp表刪除所有的雇員信息,而不只是名字為"KING"的雇員:
DECLARE
ename VARCHAR2 (10) := 'KING' ;
BEGIN
DELETE FROM emp
WHERE ename = ename;
...
在這種情況下,為了避免產生歧義,可以像下面這樣在本地變量和形式參數的前面加上類似於"my_"這樣的前綴:
DECLARE
my_ename VARCHAR2 (10);
或是使用塊標簽來進行引用限定:
<<main>>
DECLARE
ename VARCHAR2 (10) := 'KING' ;
BEGIN
DELETE FROM emp
WHERE ename = main.ename;
...
下面的例子演示了如何使用子程序名稱來限定對本地變量和形式參數的引用:
FUNCTION bonus (deptno IN NUMBER , ...) RETURN REAL IS
job CHAR (10);
BEGIN
SELECT ... WHERE deptno = bonus.deptno AND job = bonus.job;
...
五、PL/SQL標識符的作用域(scope)和可見度(visiblity)
對標識符的引用可以通過它的作用域和可見度來進行解析。標識符的作用域就是我們引用標識符的程序單元區域(塊,子程序或包)。一個標識符只在它的作 用域內可見,我們可以在作用域內不使用限定詞而直接引用它。下圖演示了變量x的作用域和可見度。x首先被聲明在封閉塊中,然后又在子塊中重新定義。
PL/SQL塊中聲明的標識符對於其所在塊來說是本地的,對於子塊來說是全局的。如果全局標識符在子塊中被重新聲明,那么,全局和本地聲明的標識符在子塊的作用域都是存在的,但是,只有本地標識符是可見的,這時如果想引用全局標識符,就需要添加限定修飾詞。
雖然我們不能在同一塊中兩次聲明同一標識符,但可以在兩個不同的塊中聲明同一標識符。這兩個標識符是互相獨立的,對其中任何一個的改變都不會影響到另一個。但是,一個塊不能引用同一級別中另外一個塊中的變量,因為對於它來說,同級塊中標識符即不是本地的,又不是全局的。
下面的例子演示了作用域規則:
DECLARE
a CHAR ;
b REAL ;
BEGIN
-- identifiers available here: a (CHAR), b
DECLARE
a INTEGER ;
c REAL ;
BEGIN
-- identifiers available here: a (INTEGER), b, c
END ;
DECLARE
d REAL ;
BEGIN
-- identifiers available here: a (CHAR), b, d
END ;
-- identifiers available here: a (CHAR), b
END ;
如果子塊中重新聲明了全局標識符,本地標識符優先權高於全局標識符,我們就不能再引用全局標識符,除非使用限定名(qualified name)。修飾詞可以是封閉塊的標簽,如下例所示:
<<outer>>
DECLARE
birtddate DATE ;
BEGIN
DECLARE
birtddate DATE ;
BEGIN
...
IF birtddate = OUTER.birtddate tdEN
...
END IF ;
...
END ;
...
END ;
如下例所示,限定修飾詞也可以是封閉子程序的名稱:
PROCEDURE check_credit(...) IS
rating NUMBER ;
FUNCTION valid(...)
RETURN BOOLEAN IS
rating NUMBER ;
BEGIN
...
IF check_credit.rating < 3 tdEN ...
END ;
BEGIN
...
END ;
但是,在同一作用域內,標簽和子程序不能使用相同的命名。
六、變量賦值
變量和常量都是在程序進入塊或子程序的時候被初始化的。默認情況下,變量都是被初始化成NULL的。除非我們為變量指定一個值,否則結果是未知的。請看下面的例子:
DECLARE
count INTEGER ;
BEGIN
-- COUNT began witd a value of NULL .
-- tdus tde expression ’COUNT + 1’ is also null.
-- So after tdis assignment, COUNT is still NULL .
count := count + 1;
為了避免這樣的情況,就要保證在賦值之前不要使用這個變量。
我們可以使用表達式來為變量賦值,例如下面的語句為變量bonus賦值:
bonus := salary * 0.15;
這里,我們需要保證的是salary * 0.15計算結果的類型必須和bonus類型保持一致。
1、布爾型(Boolean)賦值
只有TRUE、FALSE和NULL才可以賦給布爾類型的變量。例如:
BEGIN
done := FALSE ;
WHILE NOT done LOOP
...
END LOOP ;
當表達式中使用關系操作符的時候,返回結果也是布爾類型的值,所以下面的語句也是允許的。
done := (count > 500);
2、利用SQL查詢為PL/SQL變量賦值
我們可以使用SELECT語句讓Oracle為變量賦值。對於查詢字段中的每一項,在INTO子句的后面都必須有與之對應的類型兼容的變量。看一下下面這個例子:
DECLARE
emp_id emp.empno%TYPE ;
emp_name emp.ename%TYPE ;
wages NUMBER (7,2);
BEGIN
-- assign a value to emp_id here
SELECT ename, sal + comm INTO emp_name, wages
FROM emp
WHERE empno = emp_id;
...
END ;
但是,上面的用法不可以為布爾類型變量賦值。
七、PL/SQL表達式與比較
表達式由操作數和操作符構成。一個操作數就是一個變量、常量、文字或是能夠返回一個值的函數。下面是一個簡單的數學表達式:
-X / 2 + 3
像負號(-)這樣的只作用於一個操作數的操作符稱為一元操作符;而像除號(/)這樣作用於兩個操作數的操作符稱為二元操作符。PL/SQL沒有三元操作符。
最簡單的表達式就是一個能直接算出值的變量。PL/SQL按照指定的操作符和操作數來計算表達式的值,結果值的數據類型是由表達式所在的關聯文決定的。
由於操作符的運算優先級不同,表達式的計算順序也是不一樣的。下表是默認的操作符優先級順序。
操作符 運算
** 求冪
+, - 正,負
*, / 乘,除
+, -, || 加,減,連接
=, <, >, <=, >=, <>, !=, ~=, ^=,
IS NULL, LIKE, BETWEEN, IN 比較
NOT 邏輯非
AND 與
OR 或
優先級高的操作符會比優先級低的操作符先求值。下例中,兩個表達式都能計算出結果8來,因為除號的優先級要高於加號。優先級相同的操作符不會采取特殊的計算順序。
5 + 12 / 4
12 / 4 + 5
我們可以使用括號控制計算順序。例如,下面的表達式值是7,而不是11,因為括號覆蓋了默認的操作符優先順序:
(8 + 6) / 2
再看一個例子。下面的運算中,減法會在除法之前被計算,這是因為最深層的表達式總是第一個被計算的:
100 + (20 / 5 + (7 - 3))
最后,我們看看如何使用括號來改善可讀性,即使不是在必須使用括號的時候:
(salary * 0.05) + (commission * 0.25)
1、邏輯操作符
邏輯操作符有AND、OR和NOT,其中AND和OR是二元操作符,而NOT是一元操作符。下面是對應操作的真值表。
x y x AND y x OR y NOT x
TRUE TRUE TRUE TRUE FALSE
TRUE FALSE FALSE TRUE FALSE
TRUE NULL NULL TRUE FALSE
FALSE TRUE FALSE TRUE TRUE
FALSE FALSE FALSE FALSE TRUE
FALSE NULL FALSE NULL TRUE
NULL TRUE NULL TRUE NULL
NULL FALSE FALSE NULL NULL
NULL NULL NULL NULL NULL
如上面的真值表所示,AND只在操作符兩邊的操作數都是真的情況才返回TRUE。另一方面,OR操作符兩邊的操作數只要有一個值為真就能返回TRUE。NOT會返回操作數相反的值。例如NOT TRUE返回FALSE。
這里需要注意的地方是,由於NULL是一個不確定的值,所以NOT NULL的值也是無法確定的。
運算順序
當我們不用括號指定計算順序的時候,操作符的優先級就會決定操作數的計算順序。比較下面兩個表達式:
NOT (valid AND done) NOT valid AND done
如果布爾變量valid和done的值都是FALSE,那么第一個表達式的結果就為TRUE。但是,第二個表達式的結果卻是FALSE,因為NOT的優先級要比AND高。因此,第二個表達式就等價於:
(NOT valid) AND done
在下面的例子中,當valid的值為FALSE,不論done值是多少,整個表達式的值總為FALSE:
valid AND done
同樣,當下例中的valid的值為TRUE時,不論done值是多少,整個表達式的值總為TRUE:
valid OR done
短路計算
在計算邏輯表達式時,PL/SQL使用的是短路計算方法。也就是說,PL/SQL在結果可以確定下來的時候,就不會再繼續計算表達式的值了。看一下下面這個例子:
DECLARE
...
on_hand INTEGER ;
on_order INTEGER ;
BEGIN
...
IF (on_hand = 0) OR ((on_order / on_hand) < 5) tdEN
...
END IF ;
END ;
當on_hand的值是零的時候,操作符OR左面的操作數結果為TRUE,所以PL/SQL就不需要計算右邊的值了。如果PL/SQL是在應用OR操作符之前計算兩個操作數的值的話,那么右邊的操作數就會產生一個除零的錯誤。不管怎樣,依賴於"短路"計算不是一個好習慣。
比較操作符
比較操作符用於將一個表達式與另一個表達式進行比較。結果是TRUE或FALSE或NULL。最常見的就是我們在條件控制語句和SQL數據操作語句中的WHERE子句中使用比較操作符。例如:
IF quantity_on_hand > 0 tdEN
UPDATE inventory
SET quantity = quantity - 1
WHERE part_number = item_number;
ELSE
...
END IF ;
關系操作符
關系操作符可以讓我們隨意比較復雜的表達式。下面的表格列出了各種關系操作符的含義。
操作符 含義
= 等於
<>, !=, ~=, ^= 不等於
< 小於
> 大於
<= 小於等於
>= 大於等於
IS NULL 操作符
如果IS NULL所作用的操作數為空,則返回結果TRUE,否則返回結果FALSE。與空值作比較,結果總是空。所以,無論什么時候跟空值作比較,都要使用IS NULL操作符:
IF variable IS NULL tdEN ...
LIKE操作符
我們可以使用LIKE操作符來判斷一個字符、字符串或CLOB類型的值是不是與我們指定的樣式相匹配。如果樣式匹 配,LIKE就會返回TRUE,否則返回FALSE。用於LIKE匹配的樣式中,包含兩種通配符。下划線(_):精確匹配一個字符;百分號(%):匹配零 個或多個字符。如下面的例子中,如果ename的值是"JOHNSON",那么表達式結果就為TRUE:
ename LIKE 'J%SON'
BETWEEN操作符
BETWEEN操作符用於判斷目標值是否在指定的目標范圍內。例如,下面表達式的結果就為FALSE:
45 BETWEEN 38 AND 44
IN操作符
IN操作符是用於測試目標值是否是集合成員之一。其中,集合是可以包含NULL值的,但它們是被忽略的。例如,下面這個語句並不會刪除ename值為NULL的行:
DELETE FROM emp
WHERE ename IN (NULL , 'KING' , 'FORD' );
此外,如果集合中包含了NULL值,下面表達式的運算結果就是FALSE。
value NOT IN set
所以,下面這個表達式也不會刪除任何行:
DELETE FROM emp
WHERE ename NOT IN (NULL , 'king' );
連接操作符
雙豎線(||)可以當作字符連接操作符,可以將兩個字符串(CHAR、VARCHAR2、CLOB或等價的Unicode支持的類型)連接起來。例如表達式
'suit' || 'case'
返回的結果就是
'suitcase'
如果操作符兩邊的操作數都是CHAR類型,連接操作符返回的結果就是CHAR值。如果其中一個是CLOB值,操作符就返回臨時CLOB。其余情況均返回VARCHAR2類型。
2、布爾表達式
PL/SQL允許我們在SQL語句和過程語句中比較變量和常量。這樣的比較稱為布爾表達式,它們是由用關系操作符分割開的簡單或復雜表達式組成。通常,布爾表達式是由邏輯操作符AND、OR或NOT連接。布爾表達式的運算結果總是TRUE、FALSE或NULL。
在SQL語句中,布爾表達式能讓我們指定一個表中哪些行記錄可以被影響。在過程語句中,布爾表達式是條件控制的基礎。其中有三種布爾表達式:算術、字符和日期。
布爾算術表達式
我們可以使用關系表達式來比較兩個數字等或不等。例如,下面的表達式結果就為真:
number1 := 75;
number2 := 70;
number1 > number2 -- TRUE
布爾字符表達式
我們也可以比較字符的等或不等。默認情況下,比較都是基於字符串中每個字節的二進制值的。比如,下面例子中的表達式結果就為真:
string1 := 'Katdy' ;
string2 := 'Katdleen' ;
string1 > string2 -- TRUE
設置初始化參數NLS_COMP=ANSI,就能使用初始化參數NLS_SORT指定的整理序列(collating sequence)來進行比較。整理序列是一個字符集中表現字符的數字代碼(numeric code)的內部順序,如果一個字符的數字代碼比另一個大,那這個字符就比另一個字符大。關於字符在整理序列中出現的位置,每種語言都可能有不同的定義規 則。比如說,重音字母可能會因數據庫的字符集的不同而排序不同,即使每一種情況下的二進制值都相同。
布爾日期表達式
對於日期類型的比較,是按照年代的順序的。如下例,date1的值是大於date2的值的。
date1 := '01-JAN-91' ;
date2 := '31-DEC-90' ;
date1 > date2 -- TRUE
關於PL/SQL的布爾表達式使用的一些建議
一般地,不要把實型數字用於精確比較。實型數字一般都是按近似值存儲的。所以,下面的表式式值並不等於TRUE:
COUNT := 1;
IF COUNT = 1.0 tdEN
...
END IF ;
在作比較時使用括號是一個好習慣。例如,下面的這樣的表達式形式是不允許的,因為 100 < tax 的結果是布爾型,而布爾型是不能和數字500進行比較的。
100 < tax < 500 -- not allowed
解決方法是使用下面這樣的表達式:
(100 < tax) AND (tax < 500)
對於布爾型的變量來說,它的值要么為TRUE要么為FALSE,因此,對布爾型變量應用比較操作是多余的。對於下面的內容:
WHILE NOT (done = TRUE ) LOOP
...
END LOOP ;
可以簡化為:
WHILE NOT done LOOP
...
END LOOP ;
對COLB類型應用比較操作符或是用LIKE和BETWEEN這樣的函數時,可能會產生臨時LOB。我們就得確保有足夠大的表空間來容納這些臨時LOB。
3、CASE表達式
一個CASE表達式從一個或多個供選方案中選擇一個返回結果。CASE表達式使用一個選擇器來決定返回哪一個分支的結果。具體的語法形式如下:
CASE selector
WHEN expression1 tdEN result1
WHEN expression2 tdEN result2
...
WHEN expressionn tdEN resultn
[ELSE resultN+1]
END ;
選擇器后面跟着一個或多個WHEN子句,它們會被依次驗證的。一旦有一個WHEN子句滿足條件的話,剩下的分支條件就不再執行了。例如:
DECLARE
grade CHAR (1) := 'B' ;
appraisal VARCHAR2 (20);
BEGIN
appraisal := CASE grade
WHEN 'A' tdEN 'Excellent'
WHEN 'B' tdEN 'Very Good'
WHEN 'C' tdEN 'Good'
WHEN 'D' tdEN 'Fair'
WHEN 'F' tdEN 'Poor'
ELSE 'No such grade'
END ;
END ;
其中,ELSE子句是可選的,工作方式同IF語句中的ELSE子句相似。如果我們不提供ELSE子句,並且選擇器沒有匹配任何WHEN子句,表達式的返回的結果就是NULL。
這種形式的CASE表達式的另外一種使用方法就是CASE語句,其中每個WHEN子句都可以是一個完整的PL/SQL塊。
搜索式CASE表達式
PL/SQL也提供了搜索式的CASE表達式,它的語法形式如下:
CASE
WHEN expression1 tdEN result1
WHEN expression2 tdEN result2
...
WHEN expressionn tdEN resultn
[ELSE resultN+1]
END ;
搜索式CASE表達式沒有選擇器。每個WHEN子句包含一個能返回布爾值的搜索條件。例子如下:
DECLARE
grade CHAR (1);
appraisal VARCHAR2 (20);
BEGIN
...
appraisal := CASE
WHEN grade = 'A' tdEN 'Excellent'
WHEN grade = 'B' tdEN 'Very Good'
WHEN grade = 'C' tdEN 'Good'
WHEN grade = 'D' tdEN 'Fair'
WHEN grade = 'F' tdEN 'Poor'
ELSE 'No such grade'
END ;
...
END ;
搜索條件按順序計算。搜索條件的布爾值決定了哪個WHEN子句被執行。如果搜索條件的值為TRUE,它對應的WHEN子句就會被執行。只要其中一個 WHEN子句被執行,后續的搜索條件就不會被計算了。如果沒有匹配的條件,可選的ELSE就會被執行。如果沒有匹配的WHEN子句,也沒有ELSE子句, 表達式的結果就為NULL。
4、在比較和條件語句中處理NULL值
在使用NULL值時,我們一定要記住下面幾條規則,避免發生一些常見的錯誤:
比較中如果有空值的話,那么計算結果總為NULL
對空值應用邏輯操作符NOT,結果還是NULL
條件控制語句中,如果條件的運算結果值為NULL的話,與之相關的語句就不會被執行
簡單CASE語句中對於空值的判斷要使用WHEN expression IS NULL
下例中,我們期待的是sequence_of_statements被執行,因為x和y看起來就是不等的。但是,由於NULL是不確定的值,那么,x是否等於y也就無法確定了。所以,sequence_of_statements並不會執行。
x := 5;
y := NULL ;
...
IF x != y tdEN -- yields NULL, not TRUE
sequence_of_statements; -- not executed
END IF ;
同樣,下例中的sequence_of_statements也不會被執行:
a := NULL ;
b := NULL ;
...
IF a = b tdEN -- yields NULL, not TRUE
sequence_of_statements; -- not executed
END IF ;
NOT操作符
讓我們再回憶一下邏輯操作符NOT,當對一個NULL值應用NOT時,結果總是NULL。因此,下面兩段內容並不相同。
IF x > y tdEN
high := x;
ELSE
high := y;
END IF ; IF NOT x > y tdEN
high := y;
ELSE
high := x;
END IF ;
當IF條件值為FALSE或NULL時,ELSE部分就會被執行。如果x和y都不為NULL的話,兩段程序運行的效果是一樣的。但是,如果IF條件為NULL的話,第一段是給y賦值,而第二段是給x賦值。
零長度字符串
PL/SQL把零長度字符串當作空值處理,這其中包括由字符函數和布爾表達式返回的值。下面的語句均是給目標變量賦空值的操作:
null_string := TO_CHAR('');
zip_code := SUBSTR(address, 25, 0);
valid :=(NAME != '');
所以,對於檢測空字符串,要使用IS NULL操作符:
IF my_string IS NULL tdEN ...
連接操作符
連接操作符會忽略空值,例如表達式
'apple' || NULL || NULL || 'sauce'
會返回
'applesauce'
函數
如果給內置函數傳遞空值,一般也都會返回空值,但以下幾種情況除外。
函數DECODE將它的第一個參數和后面的一個或多個表達式相比較(表達式的值有可能為空),如果比較的內容相匹配,就會返回后面的結果表達式。例如在下面的例子中,如果字段rating的值為空,DECODE就會返回1000:
SELECT DECODE(rating,
NULL , 1000,
'C' , 2000,
'B' , 4000,
'A' , 5000
)
INTO credit_limit
FROM accts
WHERE acctno = my_acctno;
函數NVL在判斷出第一個參數是空的情況下,會返回第二個參數的值,否則直接返回第一個參數的值。使用方法如下:
start_date := NVL(hire_date, SYSDATE );
函數REPLACE第二個參數是NULL的時候,它就會返回第一個參數的值,不管是否有第三個參數。例如,在下面例子中,結果字符串new_string的值和old_string的值完全一樣。
new_string := REPLACE(old_string, NULL , my_string);
如果第三個參數為空的話,REPLACE就會把第一個參數中出現的第二個參數刪除,然后返回結果。如下面這個例子:
syllabified_name := 'gold - i - locks' ;
NAME := REPLACE(syllabified_name,
' - ' ,
NULL
);
運算的結果字符串是"goldilocks"。如果第二個和第三個參數都是NULL值,REPLACE就直接返回第一個參數。
八、內置函數
PL/SQL為我們提供了許多功能強大的數據操作函數。這些函數可以分為以下幾類:
錯誤報告
數字
字符
類型轉換
日期
對象引用
雜項
下面的表格是各個分類的函數。
錯誤 數字 字符 轉換 日期 對象引用 雜項
SQLCODE ABS ASCII CHARTOROWID ADD_MONtdS DEREF BFILENAME
SQLERRM ACOS CHR CONVERT CURRENT_DATE REF DECODE
ASIN CONCAT HEXTORAW CURRENT_TIMESTAMP VALUE DUMP
ATAN INITCAP RAWTOHEX DBTIMEZONE TREAT EMPTY_BLOB
ATAN2 INSTR ROWIDTOCHAR EXTRACT EMPTY_CLOB
BITAND INSTRB TO_BLOB FROM_TZ GREATEST
CEIL LENGtd TO_CHAR LAST_DAY LEAST
COS LENGtdB TO_CLOB LOCALTIMESTAMP NLS_CHARSET_DECL_LEN
COSH LOWER TO_DATE MONtdS_BETWEEN NLS_CHARSET_ID
EXP LPAD TO_MULTI_BYTE NEW_TIME NLS_CHARSET_NAME
FLOOR LTRIM TO_NCLOB NEXT_DAY NVL
LN NLS_INITCAP TO_NUMBER NUMTODSINTERVAL SYS_CONTEXT
LOG NLS_LOWER TO_SINGLE_BYTE NUMTOYMINTERVAL SYS_GUID
MOD NLSSORT ROUND UID
POWER NLS_UPPER SESSIONTIMEZONE USER
ROUND REPLACE SYSDATE USERENV
SIGN RPAD SYSTIMESTAMP VSIZE
SIN RTRIM TO_DSINTERVAL
SINH SOUNDEX TO_TIMESTAMP
SQRT SUBSTR TO_TIMESTAMP_LTZ
TAN SUBSTRB TO_TIMESTAMP_TZ
TANH TRANSLATE TO_YMINTERVAL
TRUNC TRIM TZ_OFFSET
UPPER TRUNC
除了錯誤報告(error-reporting)函數SQLCODE和SQLERRM之外,我們可以在SQL語句中使用上面所有的函數。同樣,tbw淘寶商城我們可以在過程表達式中使用除了對象引用函數DEFREF、REF、VALUE和雜函數(miscellaneous function)DECODE、DUMP、VSIZE之外的所有函數。
雖然SQL聚合函數(aggregate function,如AVG和COUNT)和SQL解析函數(analytic function,如CORR和LAG)沒有組合到PL/SQL中,但我們仍可以在SQL語句中使用它們(但不能在過程語句中使用)。