全部產品
Search
文件中心

PolarDB:控制結構

更新時間:Jul 06, 2024

本文介紹了控制結構的相關內容。

簡介

控制結構可能是PL/SQL中最有用的(以及最重要)的部分了。利用PL/SQL的控制結構,你可以以非常靈活而且強大的方法操縱本資料庫的資料。

從一個函數返回

有兩個命令讓我們能夠從函數中返回資料:RETURNRETURN NEXT

RETURN

    RETURN expression;

帶有一個運算式的RETURN用於終止函數並把expression的值返回給調用者。這種形式被用於不返回集合的PL/SQL函數。

如果一個函數返回一個標量類型,運算式的結果將被自動轉換成函數的傳回型別。但是要返回一個複合(行)值,你必須寫一個正好產生所需列集合的運算式。這可能需要使用顯式造型。

如果你聲明帶輸出參數的函數,那麼就只需要寫不帶運算式的RETURN。輸出參數變數的當前值將被返回。

如果你聲明函數返回void,一個RETURN語句可以被用來提前退出函數;但是不要在RETURN後面寫一個運算式。

一個函數的傳回值不能是未定義。如果控制到達了函數最頂層的塊而沒有碰到一個RETURN語句,那麼會發生一個執行階段錯誤。不過,這個限制不適用於帶輸出參數的函數以及返回void的函數。在這些情況中,如果頂層的塊結束,將自動執行一個RETURN語句。

一些例子:

    -- 返回一個標量類型的函數
    RETURN 1 + 2;
    RETURN scalar_var;

    -- 返回一個組合類別型的函數
    RETURN composite_type_var;
    RETURN (1, 2, 'three'::text);  -- 必須把列造型成正確的類型

RETURN NEXT以及RETURN QUERY

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

當一個PL/SQL函數被聲明為返回SETOF ``sometype,那麼遵循的過程則略有不同。在這種情況下,要返回的個體項被用一個RETURN NEXT或者RETURN QUERY命令的序列指定,並且接著會用一個不帶參數的最終RETURN命令來指示這個函數已經完成執行。RETURN NEXT可以被用於標量和複合資料型別;對於複合類型,將返回一個完整的結果“表”。RETURN QUERY將執行一個查詢的結果追加到一個函數的結果集中。在一個單一的返回集合的函數中,RETURN NEXTRETURN QUERY可以被隨意地混合,這樣它們的結果將被串接起來。

RETURN NEXTRETURN QUERY實際上不會從函數中返回 — 它們簡單地向函數的結果集中追加零或多行。然後會繼續執行PL/SQL函數中的下一條語句。隨著後繼的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() RETURN SETOF foo IS
    DECLARE
        r foo%rowtype;
    BEGIN
        FOR r IN
            SELECT * FROM foo WHERE fooid > 0
        LOOP
            -- 這裡可以做一些處理
            RETURN NEXT r; -- 返回 SELECT 的當前行
        END LOOP;
        RETURN;
    END; 

    SELECT * FROM get_all_foo();

這裡是一個使用RETURN QUERY的函數的例子:

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

        -- 因為執行還未結束,我們可以檢查是否有行被返回
        -- 如果沒有就拋出異常。
        IF NOT FOUND THEN
            RAISE EXCEPTION 'No flight at %.', $1;
        END IF;

        RETURN;
     END; 

     -- 返回可用的航班或者在沒有可用航班時拋出異常。
     SELECT * FROM get_available_flightid(CURRENT_DATE);
說明

如上所述,目前RETURN NEXTRETURN QUERY的實現在從函數返回之前會把整個結果集都儲存起來。這意味著如果一個PL/SQL函數產生一個非常大的結果集,效能可能會很差:資料將被寫到磁碟上以避免記憶體耗盡,但是函數本身在整個結果集都產生之前不會退出。將來的PL/SQL版本可能會允許使用者定義沒有這種限制的集合返回函數。目前,資料開始被寫入到磁碟的時機由組態變數 work_mem 控制。擁有足夠記憶體來儲存大型結果集的管理員可以考慮增大這個參數。

從過程中返回

過程沒有傳回值。因此,過程的結束可以不用RETURN語句。 如果想用一個RETURN語句提前結束代碼,只需寫一個沒有運算式的RETURN

如果過程有輸出參數,那麼輸出參數最終的值會被返回給調用者。

調用預存程序

PL/SQL函數,預存程序或DO塊可以使用 CALL調用預存程序。 輸出參數的處理方式與純 SQL 中CALL的工作方式不同。 預存程序的每個INOUT參數必須和CALL語句中的變數對應, 並且無論預存程序返回什麼,都會在返回後賦值給該變數。 例如:

    CREATE PROCEDURE triple(INOUT x int)
    IS
    BEGIN
        x := x * 3;
    END;

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

條件

IFCASE語句讓你可以根據某種條件執行二選其一的命令。PL/SQL有三種形式的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

    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;

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;

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
        -- 嗯,唯一的其他可能性是數字為空白
        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

    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 語句會被執行。但是如果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
        WHEN boolean-expression THEN
          statements
      [ WHEN boolean-expression THEN
          statements
        ... ]
      [ ELSE
          statements ]
    END CASE;

CASE的搜尋形式基於布林運算式真假的有條件執行。每一個WHEN子句的boolean-expression會被依次計算,直到找到一個得到的。然後相應的statements會被執行,並且接下來控制會被傳遞給END CASE之後的下一個語句(後續的WHEN運算式不會被計算)。如果沒有找到為真的結果,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,不同之處在於CASE到達一個被忽略的ELSE子句時會導致一個錯誤而不是什麼也不做。

簡單迴圈

使用LOOPEXITCONTINUEWHILEFORFOREACH語句,你可以安排PL/SQL重複一系列命令。

LOOP

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

LOOP定義一個無條件的迴圈,它會無限重複直到被EXITRETURN語句終止。可選的label可以被EXITCONTINUE語句用在嵌套迴圈中指定這些語句引用的是哪一層迴圈。

EXIT

    EXIT [ label ] [ WHEN boolean-expression ];

如果沒有給出label,那麼最內層的迴圈會被終止,然後跟在END LOOP後面的語句會被執行。如果給出了label,那麼它必須是當前或者更高層的嵌套迴圈或者語句塊的標籤。然後該命名迴圈或塊就會被終止,並且控制會轉移到該迴圈/塊相應的END之後的語句上。

如果指定了WHEN,只有boolean-expression為真時才會發生迴圈退出。否則,控制會轉移到EXIT之後的語句。

EXIT可以被用在所有類型的迴圈中,它並不限於在無條件迴圈中使用。

在和BEGIN塊一起使用時,EXIT會把控制交給塊結束後的下一個語句。需要注意的是,一個標籤必須被用於這個目的;一個沒有被標記的EXIT永遠無法被認為與一個BEGIN塊匹配(這種狀況從本資料庫 8.4 之前的發布就已經開始改變。這可能允許一個未被標記的EXIT匹配一個BEGIN塊)。

例子:

    LOOP
        -- 一些計算
        IF count > 0 THEN
            EXIT;  -- 退出迴圈
        END IF;
    END LOOP;

    LOOP
        -- 一些計算
        EXIT WHEN count > 0;  -- 和前一個例子相同的結果
    END LOOP;

    <<ablock>>
    BEGIN
        -- 一些計算
        IF stocks > 100000 THEN
            EXIT ablock;  -- 導致從 BEGIN 塊中退出
        END IF;
        -- 當stocks > 100000時,這裡的計算將被跳過
    END;

CONTINUE

    CONTINUE [ label ] [ WHEN boolean-expression ];

如果沒有給出label,最內層迴圈的下一次迭代會開始。也就是,迴圈體中剩餘的所有語句將被跳過,並且控制會返回到迴圈控製表達式(如果有)來決定是否需要另一次迴圈迭代。如果label存在,它指定應該繼續執行的迴圈的標籤。

如果指定了WHEN,該迴圈的下一次迭代只有在boolean-expression為真時才會開始。否則,控制會傳遞給CONTINUE後面的語句。

CONTINUE可以被用在所有類型的迴圈中,它並不限於在無條件迴圈中使用。

例子:

    LOOP
        -- 一些計算
        EXIT WHEN count > 100;
        CONTINUE WHEN count < 50;
        -- 一些用於 count IN [50 .. 100] 的計算
    END LOOP;

WHILE

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

只要boolean-expression被計算為真,WHILE語句就會重複一個語句序列。在每次進入到迴圈體之前都會檢查該運算式。

例如:

    WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
        -- 這裡是一些計算
    END LOOP;

    WHILE NOT done LOOP
        -- 這裡是一些計算
    END LOOP;

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
        -- 我在迴圈中將取值 1,2,3,4,5,6,7,8,9,10
    END LOOP;

    FOR i IN REVERSE 10..1 LOOP
        -- 我在迴圈中將取值 10,9,8,7,6,5,4,3,2,1
    END LOOP;

    FOR i IN REVERSE 10..1 BY 2 LOOP
        -- 我在迴圈中將取值 10,8,6,4,2
    END LOOP;

如果下界大於上界(或者在REVERSE情況下是小於),迴圈體根本不會被執行。而且不會拋出任何錯誤。

如果一個label被附加到FOR迴圈,那麼整數迴圈變數可以用一個使用那個label的限定名引用。

通過查詢結果迴圈

使用一種不同類型的FOR迴圈,你可以通過一個查詢的結果進行迭代並且操縱相應的資料。文法是:

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

target是一個記錄變數、行變數或者逗號分隔的標量變數列表。target被連續不斷被賦予來自query的每一行,並且迴圈體將為每一行執行一次。下面是一個例子:

    CREATE FUNCTION refresh_mviews() RETURN integer IS
    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;

如果迴圈被一個EXIT語句終止,那麼在迴圈之後你仍然可以訪問最後被賦予的行值。

在這類FOR語句中使用的query可以是任何返回行給調用者的 SQL 命令:最常見的是SELECT,但你也可以使用帶有RETURNING子句的INSERTUPDATEDELETE。一些EXPLAIN之類的功能性命令也可以用在這裡。

PL/SQL變數會被替換到查詢文本中,查詢計劃會被緩衝以用於可能的重用。

FOR-IN-EXECUTE語句是在行上迭代的另一種方式:

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

這個例子類似前面的形式,只不過源查詢被指定為一個字串運算式,在每次進入FOR迴圈時都會計算它並且重新規劃。這允許程式員在一個預先規劃好了的命令的速度和一個動態命令的靈活性之間進行選擇,就像一個純EXECUTE語句那樣。在使用EXECUTE時,可以通過USING將參數值插入到動態命令中。

另一種指定要對其結果迭代的查詢的方式是將它聲明為一個遊標。

通過數組迴圈

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[]) RETURN int8 IS
    DECLARE
      s int8 := 0;
      x int;
    BEGIN
      FOREACH x IN ARRAY $1
      LOOP
        s := s + x;
      END LOOP;
      RETURN s;
    END;

元素會被按照儲存順序訪問,而不管數組的維度數。儘管target通常只是一個單一變數,當通過一個組合值(記錄)的數組迴圈時,它可以是一個變數列表。在那種情況下,對每一個數組元素,變數會被從組合值的連續列賦值。

通過一個正SLICE值,FOREACH通過數組的切片而不是單一元素迭代。SLICE值必須是一個不大於數組維度數的整數常量。target變數必須是一個數組,並且它接收數組值的連續切片,其中每一個切片都有SLICE指定的維度數。這裡是一個通過一維切片迭代的例子:

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

    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/SQL函數中發生的任何錯誤都會中止函數和周圍事務的執行。你可以使用一個帶有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的閉合塊捕捉,如果沒有EXCEPTION則中止該函數的處理。

condition的名字可以是 PostgreSQL 錯誤碼中顯示的任何名字。一個分類名匹配其中所有的錯誤。特殊的條件名OTHERS匹配除了QUERY_CANCELEDASSERT_FAILURE之外的所有錯誤類型(雖然通常並不明智,還是可以用名字捕獲這兩種錯誤類型)。條件名是大小寫無關的。一個錯誤條件也可以通過SQLSTATE代碼指定,例如以下是等價的:

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

如果在選中的handler_statements內發生了新的錯誤,那麼它不能被這個EXCEPTION子句捕獲,而是被傳播出去。一個外層的EXCEPTION子句可以捕獲它。

當一個錯誤被EXCEPTION捕獲時,PL/SQL函數的局部變數會保持錯誤發生時的值,但是該塊中所有對持久資料庫狀態的改變都會被復原。例如,考慮這個片段:

    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的塊開銷大的多。因此,只在必要的時候使用EXCEPTION

UPDATE/INSERT的異常。

這個例子使用異常處理來酌情執行UPDATEINSERT。我們推薦應用使用帶有 ON CONFLICT DO UPDATEINSERT 而不是真正使用這種模式。下面的例子主要是為了展示 PL/SQL如何控制流程程:

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

    CREATE FUNCTION merge_db(key INT, data TEXT) RETURN VOID IS
    BEGIN
        LOOP
            -- 首先嘗試更新見
            UPDATE db SET b = data WHERE a = key;
            IF found THEN
                RETURN;
            END IF;
            -- 不在這裡,那麼嘗試插入該鍵
            -- 如果其他某人並發地插入同一個鍵,
            -- 我們可能得到一個唯一鍵失敗
            BEGIN
                INSERT INTO db(a,b) VALUES (key, data);
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- 什麼也不做,並且迴圈再次嘗試 UPDATE
            END;
        END LOOP;
    END; 

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

這段代碼假定unique_violation錯誤是INSERT造成,並且不是由該表上一個觸發器函數中的INSERT導致。如果在該表上有多於一個唯一索引,也可能會發生不正確的行為,因為不管哪個索引導致該錯誤它都將重試該操作。通過接下來要討論的特性來檢查被捕獲的錯誤是否為所預期的會更安全。

得到有關一個錯誤的資訊

異常處理器經常被用來標識發生的特定錯誤。有兩種方法來得到PL/SQL中當前異常的資訊:特殊變數和GET STACKED DIAGNOSTICS命令。

在一個異常處理器內,特殊變數SQLSTATE包含了對應於被拋出異常的錯誤碼。特殊變數SQLERRM包含與該異常相關的錯誤訊息。這些變數在異常處理器外是未定義的。

在一個異常處理器內,我們也可以用GET STACKED DIAGNOSTICS命令檢索有關當前異常的資訊,該命令的形式為:

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

每個item是一個關鍵詞,它標識一個被賦予給指定變數(應該具有接收該值的正確資料類型)的狀態值。錯誤診斷項表中顯示了當前可用的狀態項。

錯誤診斷項

名稱

類型

描述

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

描述產生異常時調用棧的文本行

如果異常沒有為一個項設定值,將返回一個Null 字元串。

這裡是一個例子:

    DECLARE
      text_var1 text;
      text_var2 text;
      text_var3 text;
    BEGIN
      -- 某些可能導致異常的處理
      ...
    EXCEPTION WHEN OTHERS THEN
      GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                              text_var2 = PG_EXCEPTION_DETAIL,
                              text_var3 = PG_EXCEPTION_HINT;
    END;

獲得執行位置資訊

GET DIAGNOSTICS命令檢索有關當前執行狀態的資訊(反之上文討論的GET STACKED DIAGNOSTICS命令會把有關執行狀態的資訊報告成一個以前的錯誤)。它的PG_CONTEXT狀態項可用於標識當前執行位置。狀態項PG_CONTEXT將返回一個文本字串,其中有描述該調用棧的多行文本。第一行會指向當前函數以及當前正在執行GET DIAGNOSTICS的命令。第二行及其後的行表示調用棧中更上層的調用函數。例如:

    CREATE OR REPLACE FUNCTION outer_func() RETURN integer IS
    BEGIN
      RETURN inner_func();
    END;
    

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

    SELECT outer_func();

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

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT返回同類的棧跟蹤,但是它描述檢測到錯誤的位置而不是當前位置。