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