| PostgreSQL 8.0.0 中文文件(轉譯自 PostgreSQL 中國 製作的簡體中文版本) | ||||
|---|---|---|---|---|
| Prev | Fast Backward | Chapter 35. 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 版本可能會允許用戶定義沒有這樣限制的返回集合的函數。 目前,資料開始向磁盤裡寫的時刻是由配置變量 work_mem 控制的。 擁有足夠內存的管理員如果想在內存裡儲存更大的結果集, 則可以考慮把這個參數增大一些。
IF 語句讓您可以根據某種條件執行命令。 PL/pgSQL有五種形式的IF:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF ... THEN ... ELSEIF ... 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;
ELSEIF 是 ELSIF 的別名。
使用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後面的語句上。
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; -- 導致從 BEGIN 塊裡退出
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;這裡的記錄或者行變量將相繼被賦予所有來自query(必須是一條 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(mviews.mv_name) || ' ...');
EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.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循環(整數或者返回記錄的): 方法是檢查是否有任何 .. 出現在 IN 和 LOOP 之間的圓括弧之外。 如果沒有看到 ..,那麼這個循環就是在資料行上的循環。 如果誤敲了 .. 就很可能會導致像下面這樣的錯誤訊息: "loop variable of loop over rows must be a record or row variable", 而不是我們以為會看到的簡單的語法錯誤。
預設時,一個在 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, 但是如果在 statements 裡發生了一個錯誤, 則對 statements 的進一步處理將廢棄, 控制傳遞到了 EXCEPTION 列資料表。 系統搜索這個列資料表,尋找匹配發生的錯誤的第一個元素。如果找到匹配, 則執行對應的 handler_statements,然後控制傳遞到 END 之後的下一個語句。 如果沒有找到匹配,該錯誤就會廣播出去,就好像根本沒有 EXCEPTION 子句一樣: 該錯誤可以被一個包圍塊用 EXCEPTION 捕獲,如果沒有包圍塊,則退出函數的處理。
condition 名字可以是 Appendix A 裡顯示的任何名字。 一個範疇名匹配任意該範疇裡的錯誤。特殊的條件名 OTHERS 匹配除了 QUERY_CANCELED 之外的所有錯誤類型。 (我們可以用名字捕獲 QUERY_CANCELED,不過通常是不明智的。)條件名是大小寫無關的。
如果在選中的 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 的增量值。 但是,在該塊之前的 INSERT 將不會回滾,因此最終的結果是資料庫包含 Tom Jones 而 不是 Joe Jones。
提示: 進入和退出一個包含 EXCEPTION 子句的塊要比不包含的塊開銷大的多。 因此,不必要的時候不要使用 EXCEPTION。