控制結構可能是 PL/pgSQL 中最有用(且重要)的部分。 藉由 PL/pgSQL 的控制結構,您可以用非常彈性且強大的方式操作 PostgreSQL 資料。
有兩個命令可用於從函數傳回資料:RETURN
和 RETURN NEXT
。
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
RETURN NEXT
和 RETURN QUERY
#RETURN NEXTexpression
; RETURN QUERYquery
; RETURN QUERY EXECUTEcommand-string
[ USINGexpression
[, ... ] ];
當宣告 PL/pgSQL 函數傳回 SETOF
時,要遵循的程序會略有不同。 在這種情況下,要傳回的各個項目由一系列 sometype
RETURN NEXT
或 RETURN QUERY
命令指定,然後使用沒有參數的最終 RETURN
命令來指示函數已完成執行。 RETURN NEXT
可以用於純量和複合資料型別; 對於複合結果型別,將傳回一整個 「表格」 結果。 RETURN QUERY
會將執行查詢的結果附加到函數的結果集合。 RETURN NEXT
和 RETURN QUERY
可以在單個傳回集合的函數中自由混合使用,在這種情況下,它們的結果將會被串連。
RETURN NEXT
和 RETURN QUERY
實際上並不會從函數返回,它們只是將零或多列附加到函數的結果集合。 然後,執行將繼續執行 PL/pgSQL 函數中的下一個語句。 隨著連續的 RETURN NEXT
或 RETURN 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 NEXT
和 RETURN QUERY
的實作方式是在函式回傳前,先儲存整個結果集合。這表示如果 PL/pgSQL 函式產生非常大的結果集合,效能可能會很差:資料會被寫入磁碟以避免記憶體耗盡,但函式本身要等到整個結果集合都產生後才會回傳。未來版本的 PL/pgSQL 可能會允許使用者定義沒有這種限制的回傳集合函式。目前,資料開始寫入磁碟的點由 work_mem 設定變數控制。若管理者有足夠的記憶體來儲存更大的記憶體中的結果集合,應考慮增加此參數。
程序沒有回傳值。因此,程序可以在沒有 RETURN
陳述式的情況下結束。如果您想使用 RETURN
陳述式提早結束程式碼,只需寫入不帶任何表示式的 RETURN
即可。
如果程序具有輸出參數,則輸出參數變數的最終值將回傳給呼叫者。
PL/pgSQL 函式、程序或 DO
區塊可以使用 CALL
呼叫程序。輸出參數的處理方式與純 SQL 中 CALL
的工作方式不同。程序的每個 OUT
或 INOUT
參數都必須對應到 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; $$;
對應於輸出參數的變數可以是簡單變數或複合類型變數的欄位。目前,它不能是陣列的元素。
IF
和 CASE
陳述式可讓您根據特定條件執行替代命令。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
IF-THEN
#IFboolean-expression
THENstatements
END IF;
IF-THEN
陳述式是 IF
的最簡單形式。如果條件為真,則會執行 THEN
和 END IF
之間的陳述式。否則,它們會被略過。
範例
IF v_user_id <> 0 THEN UPDATE users SET email = v_email WHERE user_id = v_user_id; END IF;
IF-THEN-ELSE
#IFboolean-expression
THENstatements
ELSEstatements
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;
IF-THEN-ELSIF
#IFboolean-expression
THENstatements
[ ELSIFboolean-expression
THENstatements
[ ELSIFboolean-expression
THENstatements
... ] ] [ ELSEstatements
] 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
麻煩得多。
CASE
#CASEsearch-expression
WHENexpression
[,expression
[ ... ]] THENstatements
[ WHENexpression
[,expression
[ ... ]] THENstatements
... ] [ ELSEstatements
] 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;
CASE
#CASE WHENboolean-expression
THENstatements
[ WHENboolean-expression
THENstatements
... ] [ ELSEstatements
] 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;
這種形式的 CASE
與 IF-THEN-ELSIF
完全等效,只是省略 ELSE
子句會導致錯誤,而不是不執行任何操作。
透過 LOOP
、EXIT
、CONTINUE
、WHILE
、FOR
和 FOREACH
陳述式,您可以安排您的 PL/pgSQL 函式重複一系列命令。
LOOP
#[ <<label
>> ] LOOPstatements
END LOOP [label
];
LOOP
定義一個無條件迴圈,它會無限重複,直到被 EXIT
或 RETURN
陳述式終止。巢狀迴圈中的 EXIT
和 CONTINUE
陳述式可以使用選用的 label
來指定這些陳述式所參考的迴圈。
EXIT
#EXIT [label
] [ WHENboolean-expression
];
如果未給定 label
,則終止最內層的迴圈,並執行 END LOOP
後面的陳述式。如果給定了 label
,則它必須是目前或某些外部層級的巢狀迴圈或區塊的標籤。然後終止命名的迴圈或區塊,並繼續執行迴圈/區塊的對應 END
後面的陳述式。
如果指定了 WHEN
,則僅當 boolean-expression
為 true 時,才會發生迴圈結束。否則,控制權會傳遞到 EXIT
之後的陳述式。
EXIT
可以與所有類型的迴圈一起使用;它不限於與無條件迴圈一起使用。
與 BEGIN
區塊一起使用時,EXIT
會將控制權傳遞到區塊結束後的下一個陳述式。請注意,必須為此目的使用標籤;未標記的 EXIT
永遠不會被視為與 BEGIN
區塊匹配。(這是與 PostgreSQL 的 pre-8.4 版本的變更,該版本允許未標記的 EXIT
與 BEGIN
區塊匹配。)
範例
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;
CONTINUE
#CONTINUE [label
] [ WHENboolean-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;
WHILE
#[ <<label
>> ] WHILEboolean-expression
LOOPstatements
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;
FOR
(整數變體) #[ <<label
>> ] FORname
IN [ REVERSE ]expression
..expression
[ BYexpression
] LOOPstatements
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
。
使用不同類型的 FOR
迴圈,您可以迭代查詢結果並相應地操作該資料。語法是:
[ <<label
>> ] FORtarget
INquery
LOOPstatements
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
子句的 INSERT
、UPDATE
、DELETE
或 MERGE
。某些公用程式指令(例如 EXPLAIN
)也可以使用。
如第 41.11.1 節和第 41.11.2 節中所述,PL/pgSQL 變數由查詢參數替換,並且查詢計畫會被快取以供可能的重複使用。
FOR-IN-EXECUTE
陳述式是迭代列的另一種方式:
[ <<label
>> ] FORtarget
IN EXECUTEtext_expression
[ USINGexpression
[, ... ] ] LOOPstatements
END LOOP [label
];
這與前面的形式類似,除了源查詢指定為字串表達式之外,該表達式在每次進入 FOR
迴圈時都會被計算和重新計畫。這允許程式設計師選擇預先計畫的查詢的速度或動態查詢的靈活性,就像普通的 EXECUTE
陳述式一樣。與 EXECUTE
一樣,參數值可以透過 USING
插入到動態指令中。
指定應迭代其結果的查詢的另一種方法是將其宣告為游標。這在第 41.7.4 節中進行了說明。
FOREACH
迴圈很像 FOR
迴圈,但是它不是迭代 SQL 查詢返回的列,而是迭代陣列值的元素。(一般而言,FOREACH
旨在迴圈處理複合值表達式的組成部分;將來可能會新增用於迴圈處理陣列以外的複合物件的變體。)用於迴圈處理陣列的 FOREACH
陳述式為:
[ <<label
>> ] FOREACHtarget
[ SLICEnumber
] IN ARRAYexpression
LOOPstatements
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}
預設情況下,在 PL/pgSQL 函式中發生的任何錯誤都會中止函式和周圍交易的執行。您可以使用帶有 EXCEPTION
子句的 BEGIN
區塊來捕獲錯誤並從中恢復。語法是 BEGIN
區塊的常規語法的擴展:
[ <<label
>> ] [ DECLAREdeclarations
] BEGINstatements
EXCEPTION WHENcondition
[ ORcondition
... ] THENhandler_statements
[ WHENcondition
[ ORcondition
... ] THENhandler_statements
... ] END;
如果沒有發生錯誤,此形式的區塊會單純地執行所有的 statements
,然後控制權會傳遞到 END
之後的下一個陳述式。但是,如果在 statements
中發生錯誤,則會放棄對 statements
的進一步處理,並且控制權會傳遞到 EXCEPTION
列表。該列表會被搜尋,以尋找與發生的錯誤相符的第一個 condition
。如果找到相符項,則會執行對應的 handler_statements
,然後控制權會傳遞到 END
之後的下一個陳述式。如果沒有找到相符項,則錯誤會像 EXCEPTION
子句根本不存在一樣傳播出去:該錯誤可以被包含 EXCEPTION
的封閉區塊捕獲,如果沒有,則會中止函式的處理。
condition
名稱可以是附錄 A中顯示的任何名稱。類別名稱符合其類別中的任何錯誤。特殊條件名稱 OTHERS
符合除 QUERY_CANCELED
和 ASSERT_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
的例外
此範例使用例外處理來執行 UPDATE
或 INSERT
,視情況而定。建議應用程式使用具有 ON CONFLICT DO UPDATE
的 INSERT
,而不是實際使用此模式。此範例主要用於說明 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
引起的。如果表上有一個以上的唯一索引,它也可能會出現異常,因為它將重試操作,而不管哪個索引導致了錯誤。通過使用接下來討論的功能來檢查捕獲的錯誤是否是預期的錯誤,可以提高安全性。
例外處理常式經常需要識別發生的特定錯誤。在 PL/pgSQL 中,有兩種方法可以取得有關目前例外的資訊:特殊變數和 GET STACKED DIAGNOSTICS
命令。
在例外處理常式中,特殊變數 SQLSTATE
包含與引發的例外相對應的錯誤碼(有關可能的錯誤碼的列表,請參閱表 A.1)。特殊變數 SQLERRM
包含與例外關聯的錯誤訊息。這些變數在例外處理常式之外未定義。
在例外處理常式中,也可以使用 GET STACKED DIAGNOSTICS
命令來取得有關目前例外的資訊,該命令的格式如下
GET STACKED DIAGNOSTICSvariable
{ = | := }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.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
會傳回種類相同的堆疊追蹤,但描述的是偵測到錯誤的位置,而不是目前的位置。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清之處,請使用此表格來回報文件問題。