| PostgreSQL 7.4 文檔 | ||||
|---|---|---|---|---|
| Prev | Fast Backward | Chapter 37. PL/pgSQL - SQL 過程語言 | Fast Forward | Next |
控制結構可能是 PL/pgSQL 中最有用的(以及最重要)的部分了。 利用 PL/pgSQL 的控制結構, 你可以以非常靈活而且強大的方法操縱 PostgreSQL 的數據。
有兩個命令可以用來從函數中返回數據:RETURN 和 RETURN NEXT。
RETURN expression;
帶表達式的 RETURN 是用于終止函數, 然後 expression 的值返回給調用者。
如果返回標量類型,那麼可以使用任何表達式。表達式的類型將被自動轉換成函數的返回類型, 就像我們在賦值中描述的那樣。 要返回一個復合(行)數值,你必須寫一個記錄或者行變量做 expression。
一個函數的返回值不能是未定義。如果控制到達了函數的最頂層的塊而沒有碰到一個 RETURN 語句, 那麼它就會發生一個錯誤。
請注意如果你聲明了該函數返回 void,那麼仍然必須聲明 RETURN 語句;但是,跟在 RETURN 後面的表達式是可選的,並且在任何情況下都會被忽略。
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/pgSQL 的 RETURN NEXT 實現在從函數返回之前把整個結果集都保存起來,就象上面描述的那樣。 這意味著如果一個 PL/pgSQL 函數生成一個非常大的結果集, 性能可能會很差:數據將被寫到磁盤上以避免內存耗盡, 但是函數在完成整個結果集的生成之前不會退出。將來的 PL/pgSQL 版本可能會允許用戶定義沒有這樣限制的返回集合的函數。 目前,數據開始向磁盤裡寫的時刻是由配置變量 sort_mem 控制的。 擁有足夠內存的管理員如果想在內存裡存儲更大的結果集, 則可以考慮把這個參數增大一些。
IF 語句讓你可以根據某種條件執行命令。 PL/pgSQL有四種形式的IF:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF boolean-expression THEN
statements
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 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;
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用. 這麼幹是可以的,但是如果我們有太多候選項需要檢查,那麼就會變得很乏味. 因此有下面的形式。
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;
使用LOOP,WHILE,FOR 和 EXIT 語句,你可以控制你的 PL/pgSQL 函數重復一系列命令。
[<<label>>]
LOOP
statements
END LOOP;LOOP 定義一個無條件的循環,無限循環,直到由EXIT或者RETURN語句終止。 可選的標簽可以由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;
[<<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;
[<<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 情況下是小于),那麼循環體將完全不被執行。 而且不會拋出任何錯誤。
使用不同類型的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 這樣的東西。