基本語句
本文介紹了基本語句的相關(guān)內(nèi)容。
賦值
為一個PL/SQL變量賦一個值可以被寫為:
variable { := | = } expression;
正如以前所解釋的,這樣一個語句中的表達式被以一個 SQL SELECT
命令被發(fā)送到主數(shù)據(jù)庫引擎的方式計算。 該表達式必須得到一個單一值(如果該變量是一個行或記錄變量, 它可能是一個行值)。該目標(biāo)變量可以是一個簡單變量( 可以選擇用一個塊名限定)、一個行或記錄變量的域或是一個簡單變量或域的數(shù)組元素。 等號(=
)可以被用來代替 PL/SQL-兼容的 :=
。
如果該表達式的結(jié)果數(shù)據(jù)類型不匹配變量的數(shù)據(jù)類型,該值將被強制為變量的類型,就好像做了賦值造型一樣。 如果沒有用于所涉及到的數(shù)據(jù)類型的賦值造型可用, PL/SQL解釋器將嘗試以文本的方式轉(zhuǎn)換結(jié)果值,也就是在應(yīng)用結(jié)果類型的輸出函數(shù)之后再應(yīng)用變量類型的輸入函數(shù)。如果結(jié)果值的字符串形式無法被輸入函數(shù)所接受,這可能會導(dǎo)致由輸入函數(shù)產(chǎn)生的運行時錯誤。
例子:
tax := subtotal * 0.06;
my_record.user_id := 20;
執(zhí)行一個沒有結(jié)果的命令
對于任何不返回行的 SQL 命令(例如沒有一個RETURNING
子句的INSERT
),你可以通過把該命令直接寫在一個 PL/SQL 函數(shù)中執(zhí)行它。
任何出現(xiàn)在該命令文本中的PL/SQL變量名被當(dāng)作一個參數(shù),并且接著該變量的當(dāng)前值被提供為運行時該參數(shù)的值。這與早前描述的對表達式的處理完全相似。
當(dāng)以這種方式執(zhí)行一個 SQL 命令時,PL/SQL會為該命令緩存并重用執(zhí)行計劃。
有時候計算一個表達式或SELECT
查詢但拋棄其結(jié)果是有用的,例如調(diào)用一個有副作用但是沒有有用的結(jié)果值的函數(shù)。在PL/SQL中要這樣做,可使用PERFORM
語句:
PERFORM query;
這會執(zhí)行query
并且丟棄掉結(jié)果。以寫一個 SQL SELECT
命令相同的方式寫該query
,并且將初始的關(guān)鍵詞SELECT
替換為PERFORM
。對于WITH
查詢,使用PERFORM
并且接著把該查詢放在圓括號中(在這種情況中,該查詢只能返回一行)。PL/SQL變量將被替換到該查詢中,正像對不返回結(jié)果的命令所做的那樣,并且計劃被以相同的方式被緩存。還有,如果該查詢產(chǎn)生至少一行,特殊變量FOUND
會被設(shè)置為真,而如果它不產(chǎn)生行則設(shè)置為假。
我們可能期望直接寫SELECT
能實現(xiàn)這個結(jié)果,但是當(dāng)前唯一被接受的方式是PERFORM
。一個能返回行的 SQL 命令(例如SELECT
)將被當(dāng)成一個錯誤拒絕,除非它像下一節(jié)中討論的有一個INTO
子句。
一個例子:
PERFORM create_mv('cs_session_page_requests_mv', my_query);
執(zhí)行一個有單一行結(jié)果的查詢
一個產(chǎn)生單一行(可能有多個列)的 SQL 命令的結(jié)果可以被賦值給一個記錄變量、行類型變量或標(biāo)量變量列表。這通過書寫基礎(chǔ) SQL 命令并增加一個INTO
子句來達成。例如:
SELECT select_expressions INTO [STRICT] target FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] target;
UPDATE ... RETURNING expressions INTO [STRICT] target;
DELETE ... RETURNING expressions INTO [STRICT] target;
其中target
可以是一個記錄變量、一個行變量或一個有逗號分隔的簡單變量和記錄/行域列表。PL/SQL變量將被替換到該查詢的剩余部分中,并且計劃會被緩存,正如之前描述的對不返回行的命令所做的。這對SELECT
、帶有RETURNING
的INSERT
/UPDATE
/DELETE
以及返回行集結(jié)果的工具命令(例如EXPLAIN
)。除了INTO
子句,SQL 命令和它在PL/SQL之外的寫法一樣。
帶INTO
的SELECT
的這種解釋和本數(shù)據(jù)庫常規(guī)的SELECT INTO
命令有很大的不同,后者的INTO
目標(biāo)是一個新創(chuàng)建的表。如果你想要在一個PL/SQL函數(shù)中從一個SELECT
的結(jié)果創(chuàng)建一個表,請使用語法CREATE TABLE ... AS SELECT
。
如果一行或一個變量列表被用作目標(biāo),該查詢的結(jié)果列必須完全匹配該結(jié)果的結(jié)構(gòu),包括數(shù)量和數(shù)據(jù)類型,否則會發(fā)生一個運行時錯誤。當(dāng)一個記錄變量是目標(biāo)時,它會自動地把自身配置成查詢結(jié)果列組成的行類型。
INTO
子句幾乎可以出現(xiàn)在 SQL 命令中的任何位置。通常它被寫成剛好在SELECT
命令中的select_expressions
列表之前或之后,或者在其他命令類型的命令最后。我們推薦你遵循這種慣例,以防PL/SQL的解析器在未來的版本中變得更嚴(yán)格。
如果STRICT
沒有在INTO
子句中被指定,那么target
將被設(shè)置為該查詢返回的第一個行,或者在該查詢不返回行時設(shè)置為空(注意除非使用了ORDER BY
,否則“第一行”的界定并不清楚)。第一行之后的任何結(jié)果行都會被拋棄。你可以檢查特殊的FOUND
變量來確定是否返回了一行:
SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
END IF;
如果指定了STRICT
選項,該查詢必須剛好返回一行或者將會報告一個運行時錯誤,該錯誤可能是NO_DATA_FOUND
(沒有行)或TOO_MANY_ROWS
(多于一行)。如果你希望捕捉該錯誤,可以使用一個異常塊,例如:
BEGIN
SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'employee % not unique', myname;
END;
成功執(zhí)行一個帶STRICT
的命令總是會將FOUND
置為真。
對于帶有RETURNING
的INSERT
/UPDATE
/DELETE
,即使沒有指定STRICT
,PL/SQL也會針對多于一個返回行的情況報告一個錯誤。這是因為沒有類似于ORDER BY
的選項可以用來決定應(yīng)該返回哪個被影響的行。
如果為該函數(shù)啟用了 If print_strict_params
,那么當(dāng)因為 STRICT
的要求沒有被滿足而拋出一個錯誤時,該錯誤消息的DETAIL
將包括傳遞給該查詢的參數(shù)信息。可以通過設(shè)置 plpgsql.print_strict_params
為所有函數(shù)更改 print_strict_params
設(shè)置,但是只有修改后被編譯的函數(shù)才會生效。也可以使用一個編譯器選項來為一個函數(shù)啟用它,例如:
CREATE FUNCTION get_userid(username text) RETURN int
IS
#print_strict_params on
DECLARE
userid int;
BEGIN
SELECT users.userid INTO STRICT userid
FROM users WHERE users.username = get_userid.username;
RETURN userid;
END;
失敗時,這個函數(shù)會產(chǎn)生一個這樣的錯誤消息
ERROR: query returned no rows
DETAIL: parameters: $1 = 'nosuchuser'
CONTEXT: PL/SQL function get_userid(text) line 6 at SQL statement
STRICT
選項匹配 Oracle PL/SQL 的SELECT INTO
和相關(guān)語句的行為。
執(zhí)行動態(tài)命令
很多時候你將想要在PL/SQL函數(shù)中產(chǎn)生動態(tài)命令,也就是每次執(zhí)行中會涉及到不同表或不同數(shù)據(jù)類型的命令。PL/SQL通常對于命令所做的緩存計劃嘗試在這種情境下無法工作。要處理這一類問題,需要提供EXECUTE
語句:
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];
其中command-string
是一個能得到一個包含要被執(zhí)行命令字符串(類型text
)的表達式。可選的target
是一個記錄變量、一個行變量或者一個逗號分隔的簡單變量以及記錄/行域的列表,該命令的結(jié)果將存儲在其中。可選的USING
表達式提供要被插入到該命令中的值。
在計算得到的命令字符串中,不會做PL/SQL變量的替換。任何所需的變量值必須在命令字符串被構(gòu)造時被插入其中,或者你可以使用下面描述的參數(shù)。
還有,對于通過EXECUTE
執(zhí)行的命令不會有計劃被緩存。該命令反而在每次運行時都會被做計劃。因此,該命令字符串可以在執(zhí)行不同表和列上動作的函數(shù)中被動態(tài)創(chuàng)建。
INTO
子句指定一個返回行的 SQL 命令的結(jié)果應(yīng)該被賦值到哪里。如果提供了一個行或變量列表,它必須完全匹配查詢結(jié)果的結(jié)構(gòu)(當(dāng)使用一個記錄變量時,它會自動把它自己配置為匹配結(jié)果結(jié)構(gòu))。如果返回多個行,只有第一個行會被賦值給INTO
變量。如果沒有返回行,NULL 會被賦值給INTO
變量。如果沒有指定INTO
變量,該查詢結(jié)果會被拋棄。
如果給出了STRICT
選項,除非該查詢剛好產(chǎn)生一行,否則將會報告一個錯誤
命令字符串可以使用參數(shù)值,它們在命令中用$1
、$2
等引用。這些符號引用在USING
子句中提供的值。這種方法常常更適合于把數(shù)據(jù)值作為文本插入到命令字符串中:它避免了將該值轉(zhuǎn)換為文本以及轉(zhuǎn)換回來的運行時負荷,并且它更不容易被 SQL 注入攻擊,因為不需要引用或轉(zhuǎn)義。一個例子是:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;
需要注意的是,參數(shù)符號只能用于數(shù)據(jù)值 — 如果想要使用動態(tài)決定的表名或列名,你必須將它們以文本形式插入到命令字符串中。例如,如果前面的那個查詢需要在一個動態(tài)選擇的表上執(zhí)行,你可以這么做:
EXECUTE 'SELECT count(*) FROM '
|| quote_ident(tabname)
|| ' WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;
一種更干凈的方法是為表名或者列名使用format()
的 %I
規(guī)范(被新行分隔的字符串會被串接起來):
EXECUTE format('SELECT count(*) FROM %I '
'WHERE inserted_by = $1 AND inserted <= $2', tabname)
INTO c
USING checked_user, checked_date;
另一個關(guān)于參數(shù)符號的限制是,它們只能在SELECT
、INSERT
、UPDATE
和DELETE
命令中工作。在另一種語句類型(通常被稱為實用語句)中,即使值是數(shù)據(jù)值,你也必須將它們以文本形式插入。
在上面第一個例子中,帶有一個簡單的常量命令字符串和一些USING
參數(shù)的EXECUTE
命令在功能上等效于直接用PL/SQL寫的命令,并且允許自動發(fā)生PL/SQL變量替換。重要的不同之處在于,EXECUTE
會在每一次執(zhí)行時根據(jù)當(dāng)前的參數(shù)值重新規(guī)劃該命令,而PL/SQL則是創(chuàng)建一個通用計劃并且將其緩存以便重用。在最佳計劃強依賴于參數(shù)值的情況中,使用EXECUTE
來明確地保證不會選擇一個通用計劃是很有幫助的。
EXECUTE
目前不支持SELECT INTO
。但是可以執(zhí)行一個純的SELECT
命令并且指定INTO
作為EXECUTE
本身的一部分。
PL/SQL中的EXECUTE
語句與EXECUTE 本數(shù)據(jù)庫服務(wù)器支持的 SQL 語句無關(guān)。服務(wù)器的EXECUTE
語句不能直接在PL/SQL函數(shù)中使用(并且也沒有必要)。
在使用動態(tài)命令時經(jīng)常不得不處理單引號的轉(zhuǎn)義。我們推薦在函數(shù)體中使用美元符號引用來引用固定的文本。
動態(tài)值需要被小心地處理,因為它們可能包含引號字符。一個使用 format()
的例子(這假設(shè)你用美元符號引用了函數(shù)體,因此引號不需要被雙寫):
EXECUTE format('UPDATE tbl SET %I = $1 '
'WHERE key = $2', colname) USING newvalue, keyvalue;
還可以直接調(diào)用引用函數(shù):
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = '
|| quote_literal(newvalue)
|| ' WHERE key = '
|| quote_literal(keyvalue);
這個例子展示了quote_ident
和quote_literal
函數(shù)的使用。為了安全,在進行一個動態(tài)查詢中的插入之前,包含列或表標(biāo)識符的表達式應(yīng)該通過quote_ident
被傳遞。如果表達式包含在被構(gòu)造出的命令中應(yīng)該是字符串的值時,它應(yīng)該通過quote_literal
被傳遞。這些函數(shù)采取適當(dāng)?shù)牟襟E來分別返回被封閉在雙引號或單引號中的文本,其中任何嵌入的特殊字符都會被正確地轉(zhuǎn)義。
因為quote_literal
被標(biāo)記為STRICT
,當(dāng)用一個空參數(shù)調(diào)用時,它總是會返回空。在上面的例子中,如果newvalue
或keyvalue
為空,整個動態(tài)查詢字符串會變成空,導(dǎo)致從EXECUTE
得到一個錯誤。可以通過使用quote_nullable
函數(shù)來避免這種問題,它工作起來和quote_literal
相同,除了用空參數(shù)調(diào)用時會返回一個字符串NULL
。例如:
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = '
|| quote_nullable(newvalue)
|| ' WHERE key = '
|| quote_nullable(keyvalue);
如果正在處理的參數(shù)值可能為空,那么通常應(yīng)該用quote_nullable
來代替quote_literal
。
通常,必須小心地確保查詢中的空值不會遞送意料之外的結(jié)果。例如如果keyvalue
為空,下面的WHERE
子句
'WHERE key = ' || quote_nullable(keyvalue)
永遠不會成功,因為在=
操作符中使用空操作數(shù)得到的結(jié)果總是為空。如果想讓空和一個普通鍵值一樣工作,你應(yīng)該將上面的命令重寫成
'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)
請注意美元符號引用只對引用固定文本有用。嘗試寫出下面這個例子是一個非常糟糕的主意:
EXECUTE 'UPDATE tbl SET '
|| quote_ident(colname)
|| ' = $$'
|| newvalue
|| '$$ WHERE key = '
|| quote_literal(keyvalue);
因為如果newvalue
的內(nèi)容碰巧含有$$
,那么這段代碼就會出問題。同樣的缺點可能適用于你選擇的任何其他美元符號引用定界符。因此,要想安全地引用事先不知道的文本,必須恰當(dāng)?shù)厥褂?code data-tag="code" class="code">quote_literal、quote_nullable
或quote_ident
。
動態(tài) SQL 語句也可以使用format
函數(shù)來安全地構(gòu)造。例如:
EXECUTE format('UPDATE tbl SET %I = %L '
'WHERE key = %L', colname, newvalue, keyvalue);
%I
等效于quote_ident
并且 %L
等效于quote_nullable
。 format
函數(shù)可以和 USING
子句一起使用:
EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
USING newvalue, keyvalue;
這種形式更好,因為變量被以它們天然的數(shù)據(jù)類型格式處理,而不是無條件地把它們轉(zhuǎn)換成文本并且通過%L
引用它們。這樣效率更高。
獲得結(jié)果狀態(tài)
有好幾種方法可以判斷一條命令的效果。第一種方法是使用GET DIAGNOSTICS
命令,其形式如下:
GET [ CURRENT ] DIAGNOSTICS variable { = | := } item [ , ... ];
這條命令允許檢索系統(tǒng)狀態(tài)指示符。CURRENT
是一個噪聲詞。每個item
是一個關(guān)鍵字, 它標(biāo)識一個要被賦予給指定變量
的狀態(tài)值(變量應(yīng)具有正確的數(shù)據(jù)類型來接收狀態(tài)值)。可用的診斷項表中展示了當(dāng)前可用的狀態(tài)項。冒號等號(:=
)可以被用來取代 SQL 標(biāo)準(zhǔn)的=
符號。例如:
GET DIAGNOSTICS integer_var = ROW_COUNT;
可用的診斷項
名稱 | 類型 | 描述 |
|
| 最近的SQL命令處理的行數(shù) |
|
| 描述當(dāng)前調(diào)用棧的文本行 |
第二種判斷命令效果的方法是檢查一個名為FOUND
的boolean
類型的特殊變量。在每一次PL/SQL函數(shù)調(diào)用時,FOUND
開始都為假。它的值會被下面的每一種類型的語句設(shè)置:
如果一個
SELECT INTO
語句賦值了一行,它將把FOUND
設(shè)置為真,如果沒有返回行則將之設(shè)置為假。如果一個
PERFORM
語句生成(并且拋棄)一行或多行,它將把FOUND
設(shè)置為真,如果沒有產(chǎn)生行則將之設(shè)置為假。如果
UPDATE
、INSERT
以及DELETE
語句影響了至少一行,它們會把FOUND
設(shè)置為真,如果沒有影響行則將之設(shè)置為假。如果一個
FETCH
語句返回了一行,它將把FOUND
設(shè)置為真,如果沒有返回行則將之設(shè)置為假。如果一個
MOVE
語句成功地重定位了游標(biāo),它將會把FOUND
設(shè)置為真,否則設(shè)置為假。如果一個
FOR
或FOREACH
語句迭代了一次或多次,它將會把FOUND
設(shè)置為真,否則設(shè)置為假。當(dāng)循環(huán)退出時,FOUND
用這種方式設(shè)置;在循環(huán)執(zhí)行中,盡管FOUND
可能被循環(huán)體中的其他語句的執(zhí)行所改變,但它不會被循環(huán)語句修改。如果查詢返回至少一行,
RETURN QUERY
和RETURN QUERY EXECUTE
語句會把FOUND
設(shè)為真, 如果沒有返回行則設(shè)置為假。
其他的PL/SQL語句不會改變FOUND
的狀態(tài)。尤其需要注意的一點是:EXECUTE
會修改GET DIAGNOSTICS
的輸出,但不會修改FOUND
的輸出。
FOUND
是每個PL/SQL函數(shù)的局部變量;任何對它的修改只影響當(dāng)前的函數(shù)。
什么也不做
有時一個什么也不做的占位語句也很有用。例如,它能夠指示 if/then/else 鏈中故意留出的空分支。可以使用NULL
語句達到這個目的:
NULL;
例如,下面的兩段代碼是等價的:
BEGIN
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN
NULL; -- 忽略錯誤
END;
BEGIN
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN -- 忽略錯誤
END;
究竟使用哪一種取決于各人的喜好。
在Oracle的 PL/SQL 中,不允許出現(xiàn)空語句列表,并且因此在這種情況下必須使用NULL
語句。而PL/SQL允許什么也不寫。