2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

41.5. 基本語句 #

在本節及後續章節中,我們將描述 PL/pgSQL 明確支援的所有語句型別。任何未被識別為這些語句型別的語句都被假定為 SQL 命令,並按照 第 41.5.2 節 中的描述傳送到主資料庫引擎執行。

41.5.1. 賦值 #

將值賦給 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;

41.5.2. 執行 SQL 命令 #

通常,任何不返回行的 SQL 命令都可以透過編寫該命令在 PL/pgSQL 函式中執行。例如,您可以編寫以下內容來建立和填充表:

CREATE TABLE mytable (id int primary key, data text);
INSERT INTO mytable VALUES (1,'one'), (2,'two');

如果命令返回行(例如 SELECT,或帶 RETURNINGINSERT/UPDATE/DELETE/MERGE),則有兩種方法可以繼續。當命令最多返回一行,或者您只關心輸出的第一行時,像往常一樣編寫命令,但新增 INTO 子句以捕獲輸出,如 第 41.5.3 節中所述。要處理所有輸出行,請將該命令寫為 FOR 迴圈的資料來源,如 第 41.6.6 節中所述。

通常,僅執行靜態定義的 SQL 命令是不夠的。通常您會希望一個命令使用不同的資料值,甚至在根本上有所不同,例如在不同時間使用不同的表名。同樣,取決於具體情況,有兩種方法可以繼續。

PL/pgSQL 變數值可以自動插入到可最佳化 SQL 命令中,這些命令包括 SELECTINSERTUPDATEDELETEMERGE 以及包含其中之一的某些實用命令,例如 EXPLAINCREATE 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);

41.5.3. 執行單行結果的命令 #

產生單行(可能包含多列)的 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;
MERGE ... RETURNING expressions INTO [STRICT] target;

其中 target 可以是記錄變數、行變數,或者逗號分隔的簡單變數和記錄/行欄位列表。PL/pgSQL 變數將像上面描述的那樣替換到命令的其餘部分(即 INTO 子句之外的所有內容),並且計劃以相同的方式快取。這適用於 SELECT、帶 RETURNINGINSERT/UPDATE/DELETE/MERGE 以及返回行集的某些實用命令,例如 EXPLAIN。除了 INTO 子句之外,SQL 命令與在 PL/pgSQL 外部編寫的命令相同。

提示

請注意,這種對帶 INTOSELECT 的解釋與 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。

對於帶 RETURNINGINSERT/UPDATE/DELETE/MERGE,即使未指定 STRICTPL/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 和相關語句的行為相匹配。

41.5.4. 執行動態命令 #

通常,您會在 PL/pgSQL 函式中生成動態命令,即那些每次執行時可能涉及不同表或不同資料型別的命令。在這種情況下,PL/pgSQL 對命令快取計劃的正常嘗試(如 第 41.11.2 節中所述)將不起作用。為了處理這類問題,提供了 EXECUTE 語句:

EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];

其中 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 命令(SELECTINSERTUPDATEDELETEMERGE 以及包含其中之一的某些命令)。在其他語句型別(統稱為實用語句)中,即使它們只是資料值,您也必須以文字形式插入值。

具有簡單常量命令字串和一些 USING 引數的 EXECUTE,如上面的第一個示例所示,在功能上等同於直接在 PL/pgSQL 中編寫命令並允許 PL/pgSQL 變數替換自動發生。重要的區別在於 EXECUTE 會在每次執行時重新計劃命令,生成一個針對當前引數值特定的計劃;而 PL/pgSQL 可能會建立通用計劃並快取它以供重用。在最佳計劃強烈依賴於引數值的情況下,使用 EXECUTE 以積極確保不選擇通用計劃可能很有幫助。

目前 EXECUTE 不支援 SELECT INTO;相反,請執行一個普通的 SELECT 命令,並將 INTO 指定為 EXECUTE 本身的一部分。

注意

PL/pgSQLEXECUTE 語句與 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_identquote_literal 函式(參見 第 9.4 節)的用法。為了安全起見,包含列或表識別符號的表示式在插入動態查詢之前應透過 quote_ident。包含應成為構造命令中文字串的值的表示式應透過 quote_literal。這些函式會執行適當的步驟,將輸入文字分別用雙引號或單引號括起來,並正確轉義嵌入的特殊字元。

由於 quote_literal 被標記為 STRICT,因此在呼叫 null 引數時它將始終返回 null。在上面的示例中,如果 newvaluekeyvalue 為 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_literalquote_nullablequote_ident

也可以使用 format 函式(參見 第 9.4.1 節)安全地構造動態 SQL 語句。例如:

EXECUTE format('UPDATE tbl SET %I = %L '
   'WHERE key = %L', colname, newvalue, keyvalue);

%I 等同於 quote_ident%L 等同於 quote_nullableformat 函式可以與 USING 子句結合使用:

EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
   USING newvalue, keyvalue;

這種形式更好,因為變數以其本機資料型別格式處理,而不是無條件地將它們轉換為文字並透過 %L 引用。它也更有效。


一個更大規模的動態命令和 EXECUTE 的示例可以在 示例 41.10 中看到,它構建並執行一個 CREATE FUNCTION 命令來定義一個新函式。

41.5.5. 獲取結果狀態 #

有幾種方法可以確定命令的效果。第一種方法是使用 GET DIAGNOSTICS 命令,其形式如下:

GET [ CURRENT ] DIAGNOSTICS variable { = | := } 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 的特殊變數,該變數的型別為 booleanFOUND 在每次 PL/pgSQL 函式呼叫開始時為 false。它由以下型別的語句設定:

  • 如果分配了一行,SELECT INTO 語句將 FOUND 設定為 true,如果未返回行,則設定為 false。

  • 如果 PERFORM 語句生成(並丟棄)一個或多個行,則將其設定為 true,否則設定為 false。

  • 如果受影響的行至少有一行,UPDATEINSERTDELETEMERGE 語句會將 FOUND 設定為 true,否則設定為 false。

  • 如果 FETCH 語句返回一行,則將其設定為 true,否則設定為 false。

  • 如果 MOVE 語句成功重新定位遊標,則設定為 true,否則為 false。

  • 如果 FORFOREACH 語句迭代一次或多次,則設定為 true,否則為 false。迴圈退出時以此方式設定 FOUND;在迴圈執行內部,FOUND 不會被迴圈語句修改,儘管它可能會被迴圈體內的其他語句執行所更改。

  • 如果查詢返回至少一行,RETURN QUERYRETURN QUERY EXECUTE 語句將 FOUND 設定為 true,否則設定為 false。

其他 PL/pgSQL 語句不改變 FOUND 的狀態。特別要注意的是,EXECUTE 會更改 GET DIAGNOSTICS 的輸出,但不會改變 FOUND

FOUND 是每個 PL/pgSQL 函式中的區域性變數;任何對它的更改只會影響當前函式。

41.5.6. 什麼都不做 #

有時,一個什麼都不做的佔位符語句很有用。例如,它可以指示 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 允許您直接寫空,而不是。

提交更正

如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。