支援的版本:目前版本 (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.6. 控制結構 #

控制結構可能是 PL/pgSQL 中最有用(且重要)的部分。 藉由 PL/pgSQL 的控制結構,您可以用非常彈性且強大的方式操作 PostgreSQL 資料。

41.6.1. 從函數返回 #

有兩個命令可用於從函數傳回資料:RETURNRETURN NEXT

41.6.1.1. RETURN #

RETURN expression;

帶有表達式的 RETURN 終止函數,並將 expression 的值傳回給呼叫者。 此形式用於不傳回集合的 PL/pgSQL 函數。

在傳回純量型別的函數中,表達式的結果將自動轉換為函數的傳回型別,如同指定所述。 但是要傳回複合(列)值,您必須編寫一個表達式,來交付精確請求的欄集合。 這可能需要使用顯式轉換。

如果您使用輸出參數宣告了該函數,請只編寫沒有表達式的 RETURN。 輸出參數變數的目前值將被傳回。

如果您宣告函數傳回 void,則可以使用 RETURN 語句提前退出函數; 但請不要在 RETURN 後面編寫表達式。

函數的傳回值不能保持未定義。 如果控制在未命中 RETURN 語句的情況下到達函數頂層區塊的末尾,則會發生執行階段錯誤。 但是,此限制不適用於具有輸出參數的函數和傳回 void 的函數。 在這些情況下,如果頂層區塊完成,則會自動執行 RETURN 語句。

一些範例

-- functions returning a scalar type
RETURN 1 + 2;
RETURN scalar_var;

-- functions returning a composite type
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- must cast columns to correct types

41.6.1.2. RETURN NEXTRETURN QUERY #

RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];

當宣告 PL/pgSQL 函數傳回 SETOF sometype 時,要遵循的程序會略有不同。 在這種情況下,要傳回的各個項目由一系列 RETURN NEXTRETURN QUERY 命令指定,然後使用沒有參數的最終 RETURN 命令來指示函數已完成執行。 RETURN NEXT 可以用於純量和複合資料型別; 對於複合結果型別,將傳回一整個 表格 結果。 RETURN QUERY 會將執行查詢的結果附加到函數的結果集合。 RETURN NEXTRETURN QUERY 可以在單個傳回集合的函數中自由混合使用,在這種情況下,它們的結果將會被串連。

RETURN NEXTRETURN QUERY 實際上並不會從函數返回,它們只是將零或多列附加到函數的結果集合。 然後,執行將繼續執行 PL/pgSQL 函數中的下一個語句。 隨著連續的 RETURN NEXTRETURN QUERY 命令被執行,結果集合將被建立。 沒有參數的最終 RETURN 會導致控制退出函數(或者您可以直接讓控制到達函數的末尾)。

RETURN QUERY 有一個變體 RETURN QUERY EXECUTE,它指定要動態執行的查詢。 參數表達式可以經由 USING 插入到計算出的查詢字串中,就像在 EXECUTE 命令中一樣。

如果您使用輸出參數宣告了該函數,請只編寫沒有表達式的 RETURN NEXT。 在每次執行時,輸出參數變數的目前值將被儲存,以作為結果的一列最終傳回。 請注意,當有多個輸出參數時,您必須宣告該函數傳回 SETOF record,或者當只有一個型別為 sometype 的輸出參數時,您必須宣告該函數傳回 SETOF sometype,以便建立一個帶有輸出參數的傳回集合函數。

這是一個使用 RETURN NEXT 的函數範例

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- can do some processing here
        RETURN NEXT r; -- return current row of SELECT
    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

這是一個使用 RETURN QUERY 的函數範例

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- Since execution is not finished, we can check whether rows were returned
    -- and raise exception if not.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END;
$BODY$
LANGUAGE plpgsql;

-- Returns available flights or raises exception if there are no
-- available flights.
SELECT * FROM get_available_flightid(CURRENT_DATE);

注意

如上所述,目前的 RETURN NEXTRETURN QUERY 的實作方式是在函式回傳前,先儲存整個結果集合。這表示如果 PL/pgSQL 函式產生非常大的結果集合,效能可能會很差:資料會被寫入磁碟以避免記憶體耗盡,但函式本身要等到整個結果集合都產生後才會回傳。未來版本的 PL/pgSQL 可能會允許使用者定義沒有這種限制的回傳集合函式。目前,資料開始寫入磁碟的點由 work_mem 設定變數控制。若管理者有足夠的記憶體來儲存更大的記憶體中的結果集合,應考慮增加此參數。

41.6.2. 從程序返回 #

程序沒有回傳值。因此,程序可以在沒有 RETURN 陳述式的情況下結束。如果您想使用 RETURN 陳述式提早結束程式碼,只需寫入不帶任何表示式的 RETURN 即可。

如果程序具有輸出參數,則輸出參數變數的最終值將回傳給呼叫者。

41.6.3. 呼叫程序 #

PL/pgSQL 函式、程序或 DO 區塊可以使用 CALL 呼叫程序。輸出參數的處理方式與純 SQL 中 CALL 的工作方式不同。程序的每個 OUTINOUT 參數都必須對應到 CALL 陳述式中的一個變數,且程序回傳的任何內容都會在程序回傳後,被賦值回該變數。例如:

CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
    x := x * 3;
END;
$$;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triple(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- prints 15
END;
$$;

對應於輸出參數的變數可以是簡單變數或複合類型變數的欄位。目前,它不能是陣列的元素。

41.6.4. 條件式 #

IFCASE 陳述式可讓您根據特定條件執行替代命令。PL/pgSQL 有三種形式的 IF

  • IF ... THEN ... END IF

  • IF ... THEN ... ELSE ... END IF

  • IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF

以及兩種形式的 CASE

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

41.6.4.1. IF-THEN #

IF boolean-expression THEN
    statements
END IF;

IF-THEN 陳述式是 IF 的最簡單形式。如果條件為真,則會執行 THENEND IF 之間的陳述式。否則,它們會被略過。

範例

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

41.6.4.2. IF-THEN-ELSE #

IF boolean-expression THEN
    statements
ELSE
    statements
END IF;

IF-THEN-ELSE 陳述式透過允許您指定一組替代陳述式來擴充 IF-THEN,這些陳述式應在條件不為真時執行。(請注意,這包括條件評估為 NULL 的情況。)

範例

IF parentid IS NULL OR parentid = ''
THEN
    RETURN fullname;
ELSE
    RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;
IF v_count > 0 THEN
    INSERT INTO users_count (count) VALUES (v_count);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

41.6.4.3. IF-THEN-ELSIF #

IF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
    ...
]
]
[ ELSE
    statements ]
END IF;

有時不只有兩種替代方案。IF-THEN-ELSIF 提供了一種方便的方法來依序檢查多種替代方案。會連續測試 IF 條件,直到找到第一個為真的條件。然後執行相關的陳述式,之後控制權會傳遞到 END IF 之後的下一個陳述式。(不會測試任何後續的 IF 條件。)如果沒有任何 IF 條件為真,則執行 ELSE 區塊(如果有的話)。

以下是一個範例

IF number = 0 THEN
    result := 'zero';
ELSIF number > 0 THEN
    result := 'positive';
ELSIF number < 0 THEN
    result := 'negative';
ELSE
    -- hmm, the only other possibility is that number is null
    result := 'NULL';
END IF;

關鍵字 ELSIF 也可以拼寫為 ELSEIF

完成相同任務的另一種方法是巢狀 IF-THEN-ELSE 陳述式,如下列範例所示:

IF demo_row.sex = 'm' THEN
    pretty_sex := 'man';
ELSE
    IF demo_row.sex = 'f' THEN
        pretty_sex := 'woman';
    END IF;
END IF;

但是,這種方法需要為每個 IF 寫入一個匹配的 END IF,因此當有很多替代方案時,它比使用 ELSIF 麻煩得多。

41.6.4.4. 簡單的 CASE #

CASE search-expression
    WHEN expression [, expression [ ... ]] THEN
      statements
  [ WHEN expression [, expression [ ... ]] THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

CASE 的簡單形式提供基於運算元相等性的條件式執行。search-expression 會被評估(一次),並連續與 WHEN 子句中的每個 expression 進行比較。如果找到匹配項,則執行對應的 statements,然後控制權傳遞到 END CASE 之後的下一個陳述式。(不會評估後續的 WHEN 表達式。)如果找不到匹配項,則執行 ELSE statements;但如果沒有 ELSE,則會引發 CASE_NOT_FOUND 例外狀況。

以下是一個簡單的範例

CASE x
    WHEN 1, 2 THEN
        msg := 'one or two';
    ELSE
        msg := 'other value than one or two';
END CASE;

41.6.4.5. 搜尋的 CASE #

CASE
    WHEN boolean-expression THEN
      statements
  [ WHEN boolean-expression THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

CASE 的搜尋形式提供基於布林表示式真假的條件式執行。依序評估每個 WHEN 子句的 boolean-expression,直到找到一個產生 true 的子句。然後執行對應的 statements,然後控制權傳遞到 END CASE 之後的下一個陳述式。(不會評估後續的 WHEN 表示式。)如果找不到 true 結果,則執行 ELSE statements;但如果沒有 ELSE,則會引發 CASE_NOT_FOUND 例外狀況。

以下是一個範例

CASE
    WHEN x BETWEEN 0 AND 10 THEN
        msg := 'value is between zero and ten';
    WHEN x BETWEEN 11 AND 20 THEN
        msg := 'value is between eleven and twenty';
END CASE;

這種形式的 CASEIF-THEN-ELSIF 完全等效,只是省略 ELSE 子句會導致錯誤,而不是不執行任何操作。

41.6.5. 簡單迴圈 #

透過 LOOPEXITCONTINUEWHILEFORFOREACH 陳述式,您可以安排您的 PL/pgSQL 函式重複一系列命令。

41.6.5.1. LOOP #

[ <<label>> ]
LOOP
    statements
END LOOP [ label ];

LOOP 定義一個無條件迴圈,它會無限重複,直到被 EXITRETURN 陳述式終止。巢狀迴圈中的 EXITCONTINUE 陳述式可以使用選用的 label 來指定這些陳述式所參考的迴圈。

41.6.5.2. EXIT #

EXIT [ label ] [ WHEN boolean-expression ];

如果未給定 label,則終止最內層的迴圈,並執行 END LOOP 後面的陳述式。如果給定了 label,則它必須是目前或某些外部層級的巢狀迴圈或區塊的標籤。然後終止命名的迴圈或區塊,並繼續執行迴圈/區塊的對應 END 後面的陳述式。

如果指定了 WHEN,則僅當 boolean-expression 為 true 時,才會發生迴圈結束。否則,控制權會傳遞到 EXIT 之後的陳述式。

EXIT 可以與所有類型的迴圈一起使用;它不限於與無條件迴圈一起使用。

BEGIN 區塊一起使用時,EXIT 會將控制權傳遞到區塊結束後的下一個陳述式。請注意,必須為此目的使用標籤;未標記的 EXIT 永遠不會被視為與 BEGIN 區塊匹配。(這是與 PostgreSQL 的 pre-8.4 版本的變更,該版本允許未標記的 EXITBEGIN 區塊匹配。)

範例

LOOP
    -- some computations
    IF count > 0 THEN
        EXIT;  -- exit loop
    END IF;
END LOOP;

LOOP
    -- some computations
    EXIT WHEN count > 0;  -- same result as previous example
END LOOP;

<<ablock>>
BEGIN
    -- some computations
    IF stocks > 100000 THEN
        EXIT ablock;  -- causes exit from the BEGIN block
    END IF;
    -- computations here will be skipped when stocks > 100000
END;

41.6.5.3. CONTINUE #

CONTINUE [ label ] [ WHEN boolean-expression ];

如果沒有給定 label,則開始最內層迴圈的下一次迭代。也就是說,迴圈體中剩餘的所有陳述式都會被跳過,並且控制權會返回到迴圈控制表達式(如果有的話),以確定是否需要另一次迴圈迭代。如果存在 label,則它指定將繼續執行的迴圈的標籤。

如果指定了 WHEN,則僅當 boolean-expression 為 true 時,才開始迴圈的下一次迭代。否則,控制權會傳遞到 CONTINUE 之後的陳述式。

CONTINUE 可以用於所有類型的迴圈;它不限於與無條件迴圈一起使用。

範例

LOOP
    -- some computations
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- some computations for count IN [50 .. 100]
END LOOP;

41.6.5.4. WHILE #

[ <<label>> ]
WHILE boolean-expression LOOP
    statements
END LOOP [ label ];

boolean-expression 的計算結果為 true 時,WHILE 陳述式會重複一系列陳述式。在每次進入迴圈體之前,都會檢查該表達式。

例如:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- some computations here
END LOOP;

WHILE NOT done LOOP
    -- some computations here
END LOOP;

41.6.5.5. FOR (整數變體) #

[ <<label>> ]
FOR name IN [ REVERSE ] expression .. expression [ BY expression ] LOOP
    statements
END LOOP [ label ];

此形式的 FOR 會建立一個迴圈,該迴圈會迭代一系列整數值。變數 name 會自動定義為 integer 類型,並且僅存在於迴圈內(迴圈內會忽略變數名稱的任何現有定義)。給定範圍的下限和上限的兩個表達式會在進入迴圈時計算一次。如果未指定 BY 子句,則迭代步長為 1,否則為 BY 子句中指定的值,該值也會在迴圈進入時計算一次。如果指定了 REVERSE,則每次迭代後會減去步長值,而不是加上步長值。

整數 FOR 迴圈的一些範例:

FOR i IN 1..10 LOOP
    -- i will take on the values 1,2,3,4,5,6,7,8,9,10 within the loop
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- i will take on the values 10,9,8,7,6,5,4,3,2,1 within the loop
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- i will take on the values 10,8,6,4,2 within the loop
END LOOP;

如果下限大於上限(或者在 REVERSE 的情況下小於上限),則根本不會執行迴圈體。不會引發錯誤。

如果 label 附加到 FOR 迴圈,則可以使用限定名稱來引用整數迴圈變數,使用該 label

41.6.6. 迴圈處理查詢結果 #

使用不同類型的 FOR 迴圈,您可以迭代查詢結果並相應地操作該資料。語法是:

[ <<label>> ]
FOR target IN query LOOP
    statements
END LOOP [ label ];

target 是一個記錄變數、列變數或逗號分隔的純量變數清單。target 會依次被賦予來自 query 的每一列,並且迴圈體會針對每一列執行。以下是一個範例:

CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Refreshing all materialized views...';

    FOR mviews IN
       SELECT n.nspname AS mv_schema,
              c.relname AS mv_name,
              pg_catalog.pg_get_userbyid(c.relowner) AS owner
         FROM pg_catalog.pg_class c
    LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
        WHERE c.relkind = 'm'
     ORDER BY 1
    LOOP

        -- Now "mviews" has one record with information about the materialized view

        RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...',
                     quote_ident(mviews.mv_schema),
                     quote_ident(mviews.mv_name),
                     quote_ident(mviews.owner);
        EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
    END LOOP;

    RAISE NOTICE 'Done refreshing materialized views.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

如果迴圈被 EXIT 陳述式終止,則最後賦值的列值在迴圈之後仍然可以存取。

在此類型的 FOR 陳述式中使用的 query 可以是任何將列返回給呼叫者的 SQL 指令:SELECT 是最常見的情況,但是您也可以使用帶有 RETURNING 子句的 INSERTUPDATEDELETEMERGE。某些公用程式指令(例如 EXPLAIN)也可以使用。

第 41.11.1 節第 41.11.2 節中所述,PL/pgSQL 變數由查詢參數替換,並且查詢計畫會被快取以供可能的重複使用。

FOR-IN-EXECUTE 陳述式是迭代列的另一種方式:

[ <<label>> ]
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
    statements
END LOOP [ label ];

這與前面的形式類似,除了源查詢指定為字串表達式之外,該表達式在每次進入 FOR 迴圈時都會被計算和重新計畫。這允許程式設計師選擇預先計畫的查詢的速度或動態查詢的靈活性,就像普通的 EXECUTE 陳述式一樣。與 EXECUTE 一樣,參數值可以透過 USING 插入到動態指令中。

指定應迭代其結果的查詢的另一種方法是將其宣告為游標。這在第 41.7.4 節中進行了說明。

41.6.7. 迴圈處理陣列 #

FOREACH 迴圈很像 FOR 迴圈,但是它不是迭代 SQL 查詢返回的列,而是迭代陣列值的元素。(一般而言,FOREACH 旨在迴圈處理複合值表達式的組成部分;將來可能會新增用於迴圈處理陣列以外的複合物件的變體。)用於迴圈處理陣列的 FOREACH 陳述式為:

[ <<label>> ]
FOREACH target [ SLICE number ] IN ARRAY expression LOOP
    statements
END LOOP [ label ];

如果沒有 SLICE,或者指定了 SLICE 0,則迴圈會迭代計算 expression 產生的陣列的各個元素。target 變數會依次被賦予每個元素值,並且迴圈體會針對每個元素執行。以下是一個迴圈處理整數陣列元素的範例:

CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

無論陣列維度如何,都會按儲存順序訪問元素。儘管 target 通常只是一個變數,但是在迴圈處理複合值(記錄)的陣列時,它可以是變數清單。在這種情況下,對於每個陣列元素,這些變數會從複合值的連續列中賦值。

使用正 SLICE 值時,FOREACH 會迭代陣列的切片,而不是單個元素。SLICE 值必須是不大於陣列維度的整數常數。target 變數必須是一個陣列,並且它會接收陣列值的連續切片,其中每個切片的維度數量由 SLICE 指定。以下是一個迴圈處理一維切片的範例:

CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'row = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTICE:  row = {1,2,3}
NOTICE:  row = {4,5,6}
NOTICE:  row = {7,8,9}
NOTICE:  row = {10,11,12}

41.6.8. 捕獲錯誤 #

預設情況下,在 PL/pgSQL 函式中發生的任何錯誤都會中止函式和周圍交易的執行。您可以使用帶有 EXCEPTION 子句的 BEGIN 區塊來捕獲錯誤並從中恢復。語法是 BEGIN 區塊的常規語法的擴展:

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
EXCEPTION
    WHEN condition [ OR condition ... ] THEN
        handler_statements
    [ WHEN condition [ OR condition ... ] THEN
          handler_statements
      ... ]
END;

如果沒有發生錯誤,此形式的區塊會單純地執行所有的 statements,然後控制權會傳遞到 END 之後的下一個陳述式。但是,如果在 statements 中發生錯誤,則會放棄對 statements 的進一步處理,並且控制權會傳遞到 EXCEPTION 列表。該列表會被搜尋,以尋找與發生的錯誤相符的第一個 condition。如果找到相符項,則會執行對應的 handler_statements,然後控制權會傳遞到 END 之後的下一個陳述式。如果沒有找到相符項,則錯誤會像 EXCEPTION 子句根本不存在一樣傳播出去:該錯誤可以被包含 EXCEPTION 的封閉區塊捕獲,如果沒有,則會中止函式的處理。

condition 名稱可以是附錄 A中顯示的任何名稱。類別名稱符合其類別中的任何錯誤。特殊條件名稱 OTHERS 符合除 QUERY_CANCELEDASSERT_FAILURE 之外的每種錯誤類型。(可以,但通常不明智地,依名稱捕獲這兩種錯誤類型。)條件名稱不區分大小寫。此外,可以使用 SQLSTATE 程式碼指定錯誤條件;例如,以下是等效的

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

如果在選定的 handler_statements 中發生新的錯誤,則此 EXCEPTION 子句無法捕獲它,而是會將其傳播出去。周圍的 EXCEPTION 子句可以捕獲它。

當錯誤被 EXCEPTION 子句捕獲時,PL/pgSQL 函式的區域變數會保持在發生錯誤時的狀態,但是區塊中對持久性資料庫狀態的所有變更都會被回滾。舉例來說,請考慮以下程式碼片段

INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
    UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
    x := x + 1;
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'caught division_by_zero';
        RETURN x;
END;

當控制到達賦值給 y 時,它會因 division_by_zero 錯誤而失敗。這將被 EXCEPTION 子句捕獲。RETURN 陳述式中傳回的值將是 x 的遞增值,但是 UPDATE 命令的效果將被回滾。但是,區塊之前的 INSERT 命令不會被回滾,因此最終結果是資料庫包含 Tom Jones 而不是 Joe Jones

提示

包含 EXCEPTION 子句的區塊比沒有該子句的區塊在進入和退出時花費更多。因此,請勿在不需要時使用 EXCEPTION

範例 41.2. 具有 UPDATE/INSERT 的例外

此範例使用例外處理來執行 UPDATEINSERT,視情況而定。建議應用程式使用具有 ON CONFLICT DO UPDATEINSERT,而不是實際使用此模式。此範例主要用於說明 PL/pgSQL 控制流程結構的用法

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Do nothing, and loop to try the UPDATE again.
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

此編碼假定 unique_violation 錯誤是由 INSERT 引起的,而不是由表上的觸發函式中的 INSERT 引起的。如果表上有一個以上的唯一索引,它也可能會出現異常,因為它將重試操作,而不管哪個索引導致了錯誤。通過使用接下來討論的功能來檢查捕獲的錯誤是否是預期的錯誤,可以提高安全性。


41.6.8.1. 取得有關錯誤的資訊 #

例外處理常式經常需要識別發生的特定錯誤。在 PL/pgSQL 中,有兩種方法可以取得有關目前例外的資訊:特殊變數和 GET STACKED DIAGNOSTICS 命令。

在例外處理常式中,特殊變數 SQLSTATE 包含與引發的例外相對應的錯誤碼(有關可能的錯誤碼的列表,請參閱表 A.1)。特殊變數 SQLERRM 包含與例外關聯的錯誤訊息。這些變數在例外處理常式之外未定義。

在例外處理常式中,也可以使用 GET STACKED DIAGNOSTICS 命令來取得有關目前例外的資訊,該命令的格式如下

GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

每個 item 都是一個關鍵字,用於識別要賦予指定 variable 的狀態值(該值應具有正確的資料類型以接收它)。目前可用的狀態項目顯示在表 41.2中。

表 41.2. 錯誤診斷項目

名稱 類型 描述
RETURNED_SQLSTATE text 例外的 SQLSTATE 錯誤碼
COLUMN_NAME text 與例外相關的欄位名稱
CONSTRAINT_NAME text 與例外相關的條件約束名稱
PG_DATATYPE_NAME text 與例外相關的資料類型名稱
MESSAGE_TEXT text 例外主要訊息的文字
TABLE_NAME text 與例外相關的表格名稱
SCHEMA_NAME text 與例外相關的綱要名稱
PG_EXCEPTION_DETAIL text 例外的詳細訊息的文字(如果有的話)
PG_EXCEPTION_HINT text 例外的提示訊息的文字(如果有的話)
PG_EXCEPTION_CONTEXT text 描述發生例外時呼叫堆疊的文字行(請參閱Section 41.6.9

如果例外沒有為項目設定值,則將傳回空字串。

以下是一個範例

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- some processing which might cause an exception
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

41.6.9. 取得執行位置資訊 #

先前於第 41.5.5 節描述的 GET DIAGNOSTICS 指令,會檢索關於目前執行狀態的資訊(而如上所述的 GET STACKED DIAGNOSTICS 指令則會報告關於先前錯誤發生時的執行狀態資訊)。其 PG_CONTEXT 狀態項目對於識別目前的執行位置很有用。PG_CONTEXT 會傳回一個文字字串,其中包含描述呼叫堆疊的文字行。第一行是指目前函式和目前執行的 GET DIAGNOSTICS 指令。第二行和任何後續的行則是指呼叫堆疊中更上層的呼叫函式。例如:

CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
  RETURN inner_func();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
  stack text;
BEGIN
  GET DIAGNOSTICS stack = PG_CONTEXT;
  RAISE NOTICE E'--- Call Stack ---\n%', stack;
  RETURN 1;
END;
$$ LANGUAGE plpgsql;

SELECT outer_func();

NOTICE:  --- Call Stack ---
PL/pgSQL function inner_func() line 5 at GET DIAGNOSTICS
PL/pgSQL function outer_func() line 3 at RETURN
CONTEXT:  PL/pgSQL function outer_func() line 3 at RETURN
 outer_func
 ------------
           1
(1 row)

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT 會傳回種類相同的堆疊追蹤,但描述的是偵測到錯誤的位置,而不是目前的位置。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清之處,請使用此表格來回報文件問題。