37.7. 控制結構

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

37.7.1. 從函數返回

有兩個命令可以用來從函數中返回數據:RETURNRETURN NEXT

37.7.1.1. RETURN

RETURN expression;

帶表達式的 RETURN 是用于終止函數, 然後 expression 的值返回給調用者。

如果返回標量類型,那麼可以使用任何表達式。表達式的類型將被自動轉換成函數的返回類型, 就像我們在賦值中描述的那樣。 要返回一個復合(行)數值,你必須寫一個記錄或者行變量做 expression

一個函數的返回值不能是未定義。如果控制到達了函數的最頂層的塊而沒有碰到一個 RETURN 語句, 那麼它就會發生一個錯誤。

請注意如果你聲明了該函數返回 void,那麼仍然必須聲明 RETURN 語句;但是,跟在 RETURN 後面的表達式是可選的,並且在任何情況下都會被忽略。

37.7.1.2. RETURN NEXT

RETURN NEXT expression;

如果一個 PL/pgSQL 函數聲明為返回 SETOF sometype, 那麼遵循的過程則略有不同。在這種情況下,要返回的獨立的項是在 RETURN NEXT 命令裡聲明的,然後最後有一個不帶參數的 RETURN 命令用于告訴我們這個函數已經完成執行了。 RETURN NEXT 可以用于標量和復合數據類型;對于後者,將返回一個完整的結果"表"

使用 RETURN NEXT 的函數應該按照下面的風格調用:

SELECT * FROM some_func();

也就是說,這個函數是用做FROM子句裡面的一個表數據源的。

RETURN NEXT 實際上並不從函數中返回; 它只是簡單地把表達式的值(或者記錄或者行變量,只要是對返回的數據類型合適的東西)保存起來。 然後執行繼續執行 PL/pgSQL 函數裡的下一條語句。 隨著後繼的 RETURN NEXT 命令的執行, 結果集就建立起來了。最後的一個不需要參數的 RETURN, 導致控制退出該函數。

注意: 目前的 PL/pgSQLRETURN NEXT 實現在從函數返回之前把整個結果集都保存起來,就象上面描述的那樣。 這意味著如果一個 PL/pgSQL 函數生成一個非常大的結果集, 性能可能會很差:數據將被寫到磁盤上以避免內存耗盡, 但是函數在完成整個結果集的生成之前不會退出。將來的 PL/pgSQL 版本可能會允許用戶定義沒有這樣限制的返回集合的函數。 目前,數據開始向磁盤裡寫的時刻是由配置變量 sort_mem 控制的。 擁有足夠內存的管理員如果想在內存裡存儲更大的結果集, 則可以考慮把這個參數增大一些。

37.7.2. 條件

IF 語句讓你可以根據某種條件執行命令。 PL/pgSQL有四種形式的IF

37.7.2.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;

37.7.2.2. IF-THEN-ELSE

IF boolean-expression THEN
    statements
ELSE
    statements
END IF;

IF-THEN-ELSE語句增加了IF-THEN的分支, 讓你可以聲明在條件計算結果為假的時候執行的語句。

例子:

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;

37.7.2.3. IF-THEN-ELSE IF

IF語句可以嵌套並且在下面的例子中:

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

如果你使用這種形式,那麼你實際上就是在另外一個IF語句的ELSE 部分嵌套了一個IF語句.因此你需要一個END IF語句 給每個嵌套的IF,另外還要一個給父IF-ELSE用. 這麼幹是可以的,但是如果我們有太多候選項需要檢查,那麼就會變得很乏味. 因此有下面的形式。

37.7.2.4. IF-THEN-ELSIF-ELSE

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

IF-THEN-ELSIF-ELSE提供了一種更方便的方法用于在一條語句中檢查許多候選條件。 形式上它和嵌套的IF-THEN-ELSE-IF-THEN命令相同, 但是只需要一個END IF

這裡是一個例子:

IF number = 0 THEN
    result := ''zero'';
ELSIF number > 0 THEN
    result := ''positive'';
ELSIF number < 0 THEN
    result := ''negative'';
ELSE
    -- 另外一個唯一的可能是它是空值
    result := ''NULL'';
END IF;

37.7.3. 簡單循環

使用LOOPWHILEFOREXIT 語句,你可以控制你的 PL/pgSQL 函數重復一系列命令。

37.7.3.1. LOOP

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

LOOP 定義一個無條件的循環,無限循環,直到由EXIT或者RETURN語句終止。 可選的標簽可以由EXIT語句使用,用于在嵌套循環中聲明應該結束哪一層循環。

37.7.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

如果沒有給出 label, 那麼退出最內層的循環,然後執行跟在END LOOP後面的語句。 如果給出 label, 那麼它必須是當前或者更高層的嵌套循環塊或者塊的標簽。 然後該命名塊或者循環就會終止,而控制落到對應循環/塊的 END 語句後面的語句上。

如果出現了WHEN,循環退出只發生在聲明的條件為真的時候, 否則控制會落到EXIT後面的語句上。

例子:

LOOP
    -- 一些計算
    IF count > 0 THEN
        EXIT;  -- exit loop
    END IF;
END LOOP;

LOOP
    -- 一些計算
    EXIT WHEN count > 0;
END LOOP;

BEGIN
    -- 一些計算
    IF stocks > 100000 THEN
        EXIT;  -- 非法,不能用 EXIT 退出到 LOOP 外面.
    END IF;
END;

37.7.3.3. WHILE

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

只要條件表達式為真,WHILE語句就會不停在一系列語句上進行循環. 條件是在每次進入循環體的時候檢查的.

比如:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- 可以在這裡做些計算
END LOOP;

WHILE NOT BOOLEAN_expression LOOP
    -- 可以在這裡做些計算
END LOOP;

37.7.3.4. FOR (整數變種)

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

這種形式的FOR對一定範圍的整數數值進行迭代的循環。 變量name 會自動定義為integer類型並且只在循環裡存在。 給出範圍上下界的兩個表達式在進入循環的時候計算一次。 迭代步進值總是為 1,但如果聲明了REVERSE就是 -1。

一些整數FOR循環的例子︰

FOR i IN 1..10 LOOP
  -- 這裡可以放一些表達式
    RAISE NOTICE ''i IS %'',i;
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- 這裡可以放一些表達式
END LOOP;

如果下界大于上界(或者是在 REVERSE 情況下是小于),那麼循環體將完全不被執行。 而且不會拋出任何錯誤。

37.7.4. 遍歷命令結果

使用不同類型的FOR循環,你可以遍歷一個命令的結果並且相應的操作哪些數據。語法是:

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

這裡的記錄或者行變量將相繼被賦予所有來自查詢(SELECT命令)的行, 並且循環體將為每行執行一次。下面是一個例子:

CREATE FUNCTION cs_refresh_mviews () RETURNS integer AS '
DECLARE
     mviews RECORD;

BEGIN
     PERFORM cs_log(''Refreshing materialized views...'');

     FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP

         -- 現在 "mviews" 裡有了一條來自 cs_materialized_views 的記錄

         PERFORM cs_log(''Refreshing materialized view '' || quote_ident(mview.mv_name) || ''...'');
         EXECUTE ''TRUNCATE TABLE  '' || quote_ident(mview.mv_name);
         EXECUTE ''INSERT INTO  '' ||  quote_ident(mview.mv_name) || '' '' || mview.mv_query;
     END LOOP;

     PERFORM cs_log(''Done refreshing materialized views.'');
     RETURN 1;
END;
' LANGUAGE plpgsql;

如果循環是用一個EXIT語句終止的,那麼在循環之後你仍然可以訪問最後賦值的行。

FOR-IN-EXECUTE語句是遍歷所有記錄的另外一種方法:

[<<label>>]
FOR record_or_row IN EXECUTE text_expression LOOP
    statements
END LOOP;

這個例子類似前面的形式,只不過源SELECT語句聲明為了一個字串表達式, 這樣它在每次進入FOR循環的時候都會重新計算和生成執行計劃。 這樣就允許程序員在一個預先規劃好了的命令所獲得的速度,和一個動態命令所獲得的靈活性(就象一個簡單的EXECUTE語句那樣)之間進行選擇。

注意: PL/pgSQL 分析器目前區分兩種類型的FOR循環(整數或者返回記錄的): 方法是檢查緊跟在FOR後面的目標變量是否聲明為了記錄/行變量。 如果不是,那麼它假設是一次整數FOR循環。 這樣,在出現真正的問題的時候可能會導致相當不明確的錯誤信息, 比如我們不小心拼錯了FOR變量的名字的時候。 典型的錯誤信息是類似 missing ".." at end of SQL expression 這樣的東西。