在本節及後續章節中,我們將描述 PL/pgSQL 明確支援的所有語句型別。任何未被識別為這些語句型別的語句都被假定為 SQL 命令,並按照 第 41.5.2 節 中的描述傳送到主資料庫引擎執行。
將值賦給 PL/pgSQL 變數的寫法如下:
variable
{ := | = }expression
;
如前所述,此語句中的表示式透過 SQL SELECT
命令計算,該命令傳送到主資料庫引擎。表示式必須產生單個值(如果變數是行或記錄變數,則可能是一個行值)。目標變數可以是簡單變數(可選擇性地用塊名限定)、行或記錄目標的欄位,或者陣列目標的元素或切片。等號(=
)可以替代 PL/SQL 相容的 :=
。
如果表示式的結果資料型別與變數的資料型別不匹配,該值將透過賦值轉換(參見 第 10.4 節)進行強制轉換。如果涉及的資料型別對之間沒有已知的賦值轉換,PL/pgSQL 直譯器將嘗試以文字形式轉換結果值,即透過應用結果型別的輸出函式,然後是變數型別的輸入函式。請注意,如果結果值的字串形式對輸入函式不可接受,這可能會導致由輸入函式生成的執行時錯誤。
示例
tax := subtotal * 0.06; my_record.user_id := 20; my_array[j] := 20; my_array[1:3] := array[1,2,3]; complex_array[n].realpart = 12.3;
通常,任何不返回行的 SQL 命令都可以透過編寫該命令在 PL/pgSQL 函式中執行。例如,您可以編寫以下內容來建立和填充表:
CREATE TABLE mytable (id int primary key, data text); INSERT INTO mytable VALUES (1,'one'), (2,'two');
如果命令返回行(例如 SELECT
,或帶 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
),則有兩種方法可以繼續。當命令最多返回一行,或者您只關心輸出的第一行時,像往常一樣編寫命令,但新增 INTO
子句以捕獲輸出,如 第 41.5.3 節中所述。要處理所有輸出行,請將該命令寫為 FOR
迴圈的資料來源,如 第 41.6.6 節中所述。
通常,僅執行靜態定義的 SQL 命令是不夠的。通常您會希望一個命令使用不同的資料值,甚至在根本上有所不同,例如在不同時間使用不同的表名。同樣,取決於具體情況,有兩種方法可以繼續。
PL/pgSQL 變數值可以自動插入到可最佳化 SQL 命令中,這些命令包括 SELECT
、INSERT
、UPDATE
、DELETE
、MERGE
以及包含其中之一的某些實用命令,例如 EXPLAIN
和 CREATE TABLE ... AS SELECT
。在這些命令中,命令文字中出現的任何 PL/pgSQL 變數名都將替換為查詢引數,然後在執行時提供變數的當前值作為引數值。這與之前為表示式描述的處理方式完全相同;有關詳細資訊,請參見 第 41.11.1 節。
以這種方式執行可最佳化 SQL 命令時,PL/pgSQL 可能會快取並重新使用該命令的執行計劃,如 第 41.11.2 節中所述。
不可最佳化 SQL 命令(也稱為實用命令)無法接受查詢引數。因此,PL/pgSQL 變數的自動替換在這種命令中不起作用。要在從 PL/pgSQL 執行的實用命令中包含非常量文字,您必須將實用命令構建為一個字串,然後 EXECUTE
它,如 第 41.5.4 節中所述。
EXECUTE
也必須用於您想以資料值提供以外的其他方式修改命令的情況,例如更改表名。
有時評估表示式或 SELECT
查詢但丟棄結果很有用,例如在呼叫具有副作用但沒有有用結果值的函式時。要在 PL/pgSQL 中執行此操作,請使用 PERFORM
語句:
PERFORM query
;
這將執行 query
並丟棄結果。以編寫 SQL SELECT
命令相同的方式編寫 query
,但將關鍵字 SELECT
替換為 PERFORM
。對於 WITH
查詢,請使用 PERFORM
,然後將查詢括在括號中。(在這種情況下,查詢只能返回一行。)PL/pgSQL 變數將像上面描述的那樣替換到查詢中,並且計劃以相同的方式快取。此外,特殊變數 FOUND
將設定為 true(如果查詢生成至少一行)或 false(如果未生成行)(參見 第 41.5.5 節)。
人們可能期望直接編寫 SELECT
會完成此結果,但目前唯一接受的方式是 PERFORM
。可能返回行的 SQL 命令(例如 SELECT
)將被拒絕為錯誤,除非它具有 INTO
子句,如下一節所述。
一個例子
PERFORM create_mv('cs_session_page_requests_mv', my_query);
產生單行(可能包含多列)的 SQL 命令的結果可以分配給記錄變數、行型別變數或標量變數列表。這可以透過編寫基本 SQL 命令並新增 INTO
子句來完成。例如:
SELECTselect_expressions
INTO [STRICT]target
FROM ...; INSERT ... RETURNINGexpressions
INTO [STRICT]target
; UPDATE ... RETURNINGexpressions
INTO [STRICT]target
; DELETE ... RETURNINGexpressions
INTO [STRICT]target
; MERGE ... RETURNINGexpressions
INTO [STRICT]target
;
其中 target
可以是記錄變數、行變數,或者逗號分隔的簡單變數和記錄/行欄位列表。PL/pgSQL 變數將像上面描述的那樣替換到命令的其餘部分(即 INTO
子句之外的所有內容),並且計劃以相同的方式快取。這適用於 SELECT
、帶 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
以及返回行集的某些實用命令,例如 EXPLAIN
。除了 INTO
子句之外,SQL 命令與在 PL/pgSQL 外部編寫的命令相同。
請注意,這種對帶 INTO
的 SELECT
的解釋與 PostgreSQL 的常規 SELECT INTO
命令完全不同,在後者中,INTO
目標是新建立的表。如果您想在 PL/pgSQL 函式中從 SELECT
結果建立表,請使用 CREATE TABLE ... AS SELECT
語法。
如果將行變數或變數列表用作目標,則命令的結果列必須在數量和資料型別上與目標的結構完全匹配,否則將發生執行時錯誤。當記錄變數是目標時,它會自動配置為匹配命令結果列的行型別。
INTO
子句幾乎可以出現在 SQL 命令的任何位置。習慣上,它要麼緊挨著 SELECT
命令中的 select_expressions
列表之前,要麼緊挨著之後,要麼對於其他命令型別放在命令的末尾。建議您遵循此約定,以防 PL/pgSQL 解析器在未來版本中變得更嚴格。
如果未在 INTO
子句中指定 STRICT
,則 target
將設定為命令返回的第一行,或者如果命令未返回行,則設定為 NULL。(請注意,除非您使用了 ORDER BY
,否則“第一行”並不明確定義。)第一行之後的任何結果行都將被丟棄。您可以檢查特殊變數 FOUND
(參見 第 41.5.5 節)以確定是否返回了行。
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
設定為 true。
對於帶 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
,即使未指定 STRICT
,PL/pgSQL 也會報告多於一行返回的錯誤。這是因為沒有像 ORDER BY
這樣的選項來確定應該返回哪個受影響的行。
如果為函式啟用了 print_strict_params
,那麼當由於 STRICT
的要求未得到滿足而引發錯誤時,錯誤訊息的 DETAIL
部分將包含有關傳遞給命令的引數的資訊。您可以透過設定 plpgsql.print_strict_params
來為所有函式更改 print_strict_params
設定,但這隻會影響後續的函式編譯。您還可以透過使用編譯器選項(例如)來為每個函式啟用它:
CREATE FUNCTION get_userid(username text) RETURNS int AS $$ #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; $$ LANGUAGE plpgsql;
失敗時,此函式可能會產生類似以下的錯誤訊息:
ERROR: query returned no rows DETAIL: parameters: username = 'nosuchuser' CONTEXT: PL/pgSQL function get_userid(text) line 6 at SQL statement
STRICT
選項與 Oracle PL/SQL 的 SELECT INTO
和相關語句的行為相匹配。
通常,您會在 PL/pgSQL 函式中生成動態命令,即那些每次執行時可能涉及不同表或不同資料型別的命令。在這種情況下,PL/pgSQL 對命令快取計劃的正常嘗試(如 第 41.11.2 節中所述)將不起作用。為了處理這類問題,提供了 EXECUTE
語句:
EXECUTEcommand-string
[ INTO [STRICT]target
] [ USINGexpression
[, ... ] ];
其中 command-string
是一個產生包含要執行的命令的字串(型別為 text
)的表示式。可選的 target
是一個記錄變數、一個行變數,或者一個逗號分隔的簡單變數和記錄/行欄位列表,命令的結果將儲存在其中。可選的 USING
表示式提供要插入到命令中的值。
不會對計算出的命令字串進行 PL/pgSQL 變數替換。任何必需的變數值都必須在構建命令字串時插入;或者您可以使用如下所述的引數。
此外,透過 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;
(此示例依賴於 SQL 規則,即用換行符分隔的字串文字會被隱式連線。)
引數符號的另一個限制是它們只能用於可最佳化 SQL 命令(SELECT
、INSERT
、UPDATE
、DELETE
、MERGE
以及包含其中之一的某些命令)。在其他語句型別(統稱為實用語句)中,即使它們只是資料值,您也必須以文字形式插入值。
具有簡單常量命令字串和一些 USING
引數的 EXECUTE
,如上面的第一個示例所示,在功能上等同於直接在 PL/pgSQL 中編寫命令並允許 PL/pgSQL 變數替換自動發生。重要的區別在於 EXECUTE
會在每次執行時重新計劃命令,生成一個針對當前引數值特定的計劃;而 PL/pgSQL 可能會建立通用計劃並快取它以供重用。在最佳計劃強烈依賴於引數值的情況下,使用 EXECUTE
以積極確保不選擇通用計劃可能很有幫助。
目前 EXECUTE
不支援 SELECT INTO
;相反,請執行一個普通的 SELECT
命令,並將 INTO
指定為 EXECUTE
本身的一部分。
PL/pgSQL 的 EXECUTE
語句與 PostgreSQL 伺服器支援的 EXECUTE
SQL 語句無關。伺服器的 EXECUTE
語句不能直接在 PL/pgSQL 函式中使用(且不需要)。
示例 41.1. 動態查詢中的引用值
處理動態命令時,您通常需要處理單引號的轉義。在函式體中引用固定文字的推薦方法是美元引用。(如果您有不使用美元引用的舊程式碼,請參閱 第 41.12.1 節中的概述,這可以為您翻譯 said 程式碼以採用更合理的方案節省一些工作。)
動態值需要仔細處理,因為它們可能包含引號字元。使用 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
函式(參見 第 9.4 節)的用法。為了安全起見,包含列或表識別符號的表示式在插入動態查詢之前應透過 quote_ident
。包含應成為構造命令中文字串的值的表示式應透過 quote_literal
。這些函式會執行適當的步驟,將輸入文字分別用雙引號或單引號括起來,並正確轉義嵌入的特殊字元。
由於 quote_literal
被標記為 STRICT
,因此在呼叫 null 引數時它將始終返回 null。在上面的示例中,如果 newvalue
或 keyvalue
為 null,則整個動態查詢字串將變為 null,從而導致 EXECUTE
出錯。您可以透過使用 quote_nullable
函式來避免此問題,該函式的作用與 quote_literal
相同,除了在呼叫 null 引數時它會返回字串 NULL
。例如:
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = ' || quote_nullable(newvalue) || ' WHERE key = ' || quote_nullable(keyvalue);
如果您處理可能為 null 的值,通常應使用 quote_nullable
而不是 quote_literal
。
一如既往,必須小心確保查詢中的 null 值不會產生意外結果。例如,WHERE
子句:
'WHERE key = ' || quote_nullable(keyvalue)
如果 keyvalue
為 null,將永遠不會成功,因為使用等號運算子 =
與 null 運算元的結果始終為 null。如果您希望 null 像普通鍵值一樣工作,則需要將上述內容重寫為:
'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)
(目前,IS NOT DISTINCT FROM
的處理效率遠不如 =
,所以除非必須,否則不要這樣做。有關 nulls 和 IS DISTINCT
的更多資訊,請參見 第 9.2 節。)
請注意,美元引用僅適用於引用固定文字。嘗試這樣編寫示例將是一個非常糟糕的主意:
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = $$' || newvalue || '$$ WHERE key = ' || quote_literal(keyvalue);
因為它會在 newvalue
的內容碰巧包含 $$
時中斷。同樣的問題也會影響您選擇的任何其他美元引用分隔符。因此,為了安全地引用未知文字,您必須根據需要使用 quote_literal
、quote_nullable
或 quote_ident
。
也可以使用 format
函式(參見 第 9.4.1 節)安全地構造動態 SQL 語句。例如:
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
引用。它也更有效。
一個更大規模的動態命令和 EXECUTE
的示例可以在 示例 41.10 中看到,它構建並執行一個 CREATE FUNCTION
命令來定義一個新函式。
有幾種方法可以確定命令的效果。第一種方法是使用 GET DIAGNOSTICS
命令,其形式如下:
GET [ CURRENT ] DIAGNOSTICSvariable
{ = | := }item
[ , ... ];
此命令允許檢索系統狀態指示符。CURRENT
是一個無關詞(但請參閱 第 41.6.8.1 節中的 GET STACKED DIAGNOSTICS
)。每個 item
都是一個關鍵字,用於標識要分配給指定 variable
的狀態值(該變數應具有接收它的正確資料型別)。當前可用的狀態項顯示在 表 41.1 中。冒號等號(:=
)可以替代 SQL 標準 =
標記。一個例子:
GET DIAGNOSTICS integer_var = ROW_COUNT;
表 41.1. 可用的診斷項
名稱 | 型別 | 描述 |
---|---|---|
ROW_COUNT |
bigint |
最近處理的行數SQLcommand |
PG_CONTEXT |
text |
描述當前呼叫堆疊的文字行(參見 第 41.6.9 節) |
PG_ROUTINE_OID |
oid |
當前函式的 OID |
確定命令效果的第二種方法是檢查名為 FOUND
的特殊變數,該變數的型別為 boolean
。FOUND
在每次 PL/pgSQL 函式呼叫開始時為 false。它由以下型別的語句設定:
如果分配了一行,SELECT INTO
語句將 FOUND
設定為 true,如果未返回行,則設定為 false。
如果 PERFORM
語句生成(並丟棄)一個或多個行,則將其設定為 true,否則設定為 false。
如果受影響的行至少有一行,UPDATE
、INSERT
、DELETE
和 MERGE
語句會將 FOUND
設定為 true,否則設定為 false。
如果 FETCH
語句返回一行,則將其設定為 true,否則設定為 false。
如果 MOVE
語句成功重新定位遊標,則設定為 true,否則為 false。
如果 FOR
或 FOREACH
語句迭代一次或多次,則設定為 true,否則為 false。迴圈退出時以此方式設定 FOUND
;在迴圈執行內部,FOUND
不會被迴圈語句修改,儘管它可能會被迴圈體內的其他語句執行所更改。
如果查詢返回至少一行,RETURN QUERY
和 RETURN QUERY EXECUTE
語句將 FOUND
設定為 true,否則設定為 false。
其他 PL/pgSQL 語句不改變 FOUND
的狀態。特別要注意的是,EXECUTE
會更改 GET DIAGNOSTICS
的輸出,但不會改變 FOUND
。
FOUND
是每個 PL/pgSQL 函式中的區域性變數;任何對它的更改只會影響當前函式。
有時,一個什麼都不做的佔位符語句很有用。例如,它可以指示 if/then/else 鏈中的一個分支是故意留空的。為此,請使用 NULL
語句:
NULL;
例如,以下兩個程式碼片段是等效的:
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN NULL; -- ignore the error END;
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN -- ignore the error END;
哪個更好取決於個人喜好。
在 Oracle 的 PL/SQL 中,不允許空語句列表,因此 NULL
語句對於這種情況是必需的。 PL/pgSQL 允許您直接寫空,而不是。
如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。