W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
控制結(jié)構(gòu)可能是PL/pgSQL中最有用的(以及最重要)的部分了。利用PL/pgSQL的控制結(jié)構(gòu),你可以以非常靈活而且強(qiáng)大的方法操縱PostgreSQL的數(shù)據(jù)。
有兩個(gè)命令讓我們能夠從函數(shù)中返回?cái)?shù)據(jù):RETURN
和RETURN NEXT
。
RETURN
RETURN expression
;
帶有一個(gè)表達(dá)式的RETURN
用于終止函數(shù)并把expression
的值返回給調(diào)用者。這種形式被用于不返回集合的PL/pgSQL函數(shù)。
如果一個(gè)函數(shù)返回一個(gè)標(biāo)量類型,表達(dá)式的結(jié)果將被自動(dòng)轉(zhuǎn)換成函數(shù)的返回類型。但是要返回一個(gè)復(fù)合(行)值,你必須寫一個(gè)正好產(chǎn)生所需列集合的表達(dá)式。這可能需要使用顯式造型。
如果你聲明帶輸出參數(shù)的函數(shù),那么就只需要寫不帶表達(dá)式的RETURN
。輸出參數(shù)變量的當(dāng)前值將被返回。
如果你聲明函數(shù)返回void
,一個(gè)RETURN
語(yǔ)句可以被用來(lái)提前退出函數(shù);但是不要在RETURN
后面寫一個(gè)表達(dá)式。
一個(gè)函數(shù)的返回值不能是未定義。如果控制到達(dá)了函數(shù)最頂層的塊而沒(méi)有碰到一個(gè)RETURN
語(yǔ)句,那么會(huì)發(fā)生一個(gè)運(yùn)行時(shí)錯(cuò)誤。不過(guò),這個(gè)限制不適用于帶輸出參數(shù)的函數(shù)以及返回void
的函數(shù)。在這些情況中,如果頂層的塊結(jié)束,將自動(dòng)執(zhí)行一個(gè)RETURN
語(yǔ)句。
一些例子:
-- 返回一個(gè)標(biāo)量類型的函數(shù)
RETURN 1 + 2;
RETURN scalar_var;
-- 返回一個(gè)組合類型的函數(shù)
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
[, ... ] ];
當(dāng)一個(gè)PL/pgSQL函數(shù)被聲明為返回SETOF
,那么遵循的過(guò)程則略有不同。在這種情況下,要返回的個(gè)體項(xiàng)被用一個(gè)sometype
RETURN NEXT
或者RETURN QUERY
命令的序列指定,并且接著會(huì)用一個(gè)不帶參數(shù)的最終RETURN
命令來(lái)指示這個(gè)函數(shù)已經(jīng)完成執(zhí)行。RETURN NEXT
可以被用于標(biāo)量和復(fù)合數(shù)據(jù)類型;對(duì)于復(fù)合類型,將返回一個(gè)完整的結(jié)果
“表”。RETURN QUERY
將執(zhí)行一個(gè)查詢的結(jié)果追加到一個(gè)函數(shù)的結(jié)果集中。在一個(gè)單一的返回集合的函數(shù)中,RETURN NEXT
和RETURN QUERY
可以被隨意地混合,這樣它們的結(jié)果將被串接起來(lái)。
RETURN NEXT
和RETURN QUERY
實(shí)際上不會(huì)從函數(shù)中返回 — 它們簡(jiǎn)單地向函數(shù)的結(jié)果集中追加零或多行。然后會(huì)繼續(xù)執(zhí)行PL/pgSQL函數(shù)中的下一條語(yǔ)句。隨著后繼的RETURN NEXT
和RETURN QUERY
命令的執(zhí)行,結(jié)果集就建立起來(lái)了。最后一個(gè)
RETURN
(應(yīng)該沒(méi)有參數(shù))會(huì)導(dǎo)致控制退出該函數(shù)(或者你可以讓控制到達(dá)函數(shù)的結(jié)尾)。
RETURN QUERY
有一種變體RETURN QUERY EXECUTE
,它可以動(dòng)態(tài)指定要被執(zhí)行的查詢??梢酝ㄟ^(guò)USING
向計(jì)算出的查詢字符串插入?yún)?shù)表達(dá)式,這和在EXECUTE
命令中的方式相同。
如果你聲明函數(shù)帶有輸出參數(shù),只需要寫不帶表達(dá)式的RETURN NEXT
。在每一次執(zhí)行時(shí),輸出參數(shù)變量的當(dāng)前值將被保存下來(lái)用于最終返回為結(jié)果的一行。注意為了創(chuàng)建一個(gè)帶有輸出參數(shù)的集合返回函數(shù),在有多個(gè)輸出參數(shù)時(shí),你必須聲明函數(shù)為返回SETOF record
;或者如果只有一個(gè)類型為sometype
的輸出參數(shù)時(shí),聲明函數(shù)為
SETOF
。sometype
下面是一個(gè)使用RETURN NEXT
的函數(shù)例子:
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
-- 這里可以做一些處理
RETURN NEXT r; -- 返回 SELECT 的當(dāng)前行
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
SELECT * FROM get_all_foo();
這里是一個(gè)使用RETURN QUERY
的函數(shù)的例子:
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);
-- 因?yàn)閳?zhí)行還未結(jié)束,我們可以檢查是否有行被返回
-- 如果沒(méi)有就拋出異常。
IF NOT FOUND THEN
RAISE EXCEPTION 'No flight at %.', $1;
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
-- 返回可用的航班或者在沒(méi)有可用航班時(shí)拋出異常。
SELECT * FROM get_available_flightid(CURRENT_DATE);
如上所述,目前RETURN NEXT
和RETURN QUERY
的實(shí)現(xiàn)在從函數(shù)返回之前會(huì)把整個(gè)結(jié)果集都保存起來(lái)。這意味著如果一個(gè)PL/pgSQL函數(shù)生成一個(gè)非常大的結(jié)果集,性能可能會(huì)很差:數(shù)據(jù)將被寫到磁盤上以避免內(nèi)存耗盡,但是函數(shù)本身在整個(gè)結(jié)果集都生成之前不會(huì)退出。將來(lái)的PL/pgSQL版本可能會(huì)允許用戶定義沒(méi)有這種限制的集合返回函數(shù)。目前,數(shù)據(jù)開(kāi)始被寫入到磁盤的時(shí)機(jī)由配置變量
work_mem控制。擁有足夠內(nèi)存來(lái)存儲(chǔ)大型結(jié)果集的管理員可以考慮增大這個(gè)參數(shù)。
過(guò)程沒(méi)有返回值。因此,過(guò)程的結(jié)束可以不用RETURN
語(yǔ)句。 如果想用一個(gè)RETURN
語(yǔ)句提前退出代碼,只需寫一個(gè)沒(méi)有表達(dá)式的RETURN
。
如果過(guò)程有輸出參數(shù),那么輸出參數(shù)最終的值會(huì)被返回給調(diào)用者。
PL/pgSQL函數(shù),存儲(chǔ)過(guò)程或DO
塊可以使用
CALL
調(diào)用存儲(chǔ)過(guò)程。 輸出參數(shù)的處理方式與純SQL中CALL
的工作方式不同。 存儲(chǔ)過(guò)程的每個(gè)INOUT
參數(shù)必須和CALL
語(yǔ)句中的變量對(duì)應(yīng),
并且無(wú)論存儲(chǔ)過(guò)程返回什么,都會(huì)在返回后賦值給該變量。 例如:
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
語(yǔ)句讓你可以根據(jù)某種條件執(zhí)行二選其一的命令。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
IF boolean-expression
THEN
statements
END IF;
IF-THEN
語(yǔ)句是IF
的最簡(jiǎn)單形式。 如果條件為真,在THEN
和END IF
之間的語(yǔ)句將被執(zhí)行。否則,將忽略它們。
例子:
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
語(yǔ)句對(duì)IF-THEN
進(jìn)行了增加,它讓你能夠指定一組在條件不為真時(shí)應(yīng)該被執(zhí)行的語(yǔ)句(注意這也包括條件為 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;
有時(shí)會(huì)有多于兩種選擇。IF-THEN-ELSIF
則提供了一個(gè)簡(jiǎn)便的方法來(lái)檢查多個(gè)條件。IF
條件會(huì)被一個(gè)接一個(gè)測(cè)試,直到找到第一個(gè)為真的。然后執(zhí)行相關(guān)語(yǔ)句,然后控制會(huì)被交給END IF
之后的下一個(gè)語(yǔ)句(后續(xù)的任何IF
條件不會(huì)被測(cè)試)。如果沒(méi)有一個(gè)
IF
條件為真,那么ELSE
塊(如果有)將被執(zhí)行。
這里有一個(gè)例子:
IF number = 0 THEN
result := 'zero';
ELSIF number > 0 THEN
result := 'positive';
ELSIF number < 0 THEN
result := 'negative';
ELSE
-- 嗯,唯一的其他可能性是數(shù)字為空
result := 'NULL';
END IF;
關(guān)鍵詞ELSIF
也可以被拼寫成ELSEIF
。
另一個(gè)可以完成相同任務(wù)的方法是嵌套IF-THEN-ELSE
語(yǔ)句,如下例:
IF demo_row.sex = 'm' THEN
pretty_sex := 'man';
ELSE
IF demo_row.sex = 'f' THEN
pretty_sex := 'woman';
END IF;
END IF;
不過(guò),這種方法需要為每個(gè)IF
都寫一個(gè)匹配的END IF
,因此當(dāng)有很多選擇時(shí),這種方法比使用ELSIF
要麻煩得多。
CASE
CASE search-expression
WHEN expression
[, expression
[ ... ]] THEN
statements
[ WHEN expression
[, expression
[ ... ]] THEN
statements
... ]
[ ELSE
statements
]
END CASE;
CASE
的簡(jiǎn)單形式提供了基于操作數(shù)等值判斷的有條件執(zhí)行。search-expression
會(huì)被計(jì)算(一次)并且一個(gè)接一個(gè)地與WHEN
子句中的每個(gè)expression
比較。如果找到一個(gè)匹配,那么相應(yīng)的statements
會(huì)被執(zhí)行,并且接著控制會(huì)被交給
END CASE
之后的下一個(gè)語(yǔ)句(后續(xù)的WHEN
表達(dá)式不會(huì)被計(jì)算)。如果沒(méi)有找到匹配,ELSE
語(yǔ)句
會(huì)被執(zhí)行。但是如果ELSE
不存在,將會(huì)拋出一個(gè)CASE_NOT_FOUND
異常。
這里是一個(gè)簡(jiǎn)單的例子:
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
的搜索形式基于布爾表達(dá)式真假的有條件執(zhí)行。每一個(gè)WHEN
子句的boolean-expression
會(huì)被依次計(jì)算,直到找到一個(gè)得到真
的。然后相應(yīng)的statements
會(huì)被執(zhí)行,并且接下來(lái)控制會(huì)被傳遞給
END CASE
之后的下一個(gè)語(yǔ)句(后續(xù)的WHEN
表達(dá)式不會(huì)被計(jì)算)。如果沒(méi)有找到為真的結(jié)果,ELSE
statements
會(huì)被執(zhí)行。但是如果ELSE
不存在,那么將會(huì)拋出一個(gè)CASE_NOT_FOUND
異常。
這里是一個(gè)例子:
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
整體上等價(jià)于IF-THEN-ELSIF
,不同之處在于CASE
到達(dá)一個(gè)被忽略的ELSE
子句時(shí)會(huì)導(dǎo)致一個(gè)錯(cuò)誤而不是什么也不做。
使用LOOP
、EXIT
、CONTINUE
、WHILE
、FOR
和FOREACH
語(yǔ)句,你可以安排PL/pgSQL重復(fù)一系列命令。
LOOP
[ <<label
>> ]
LOOP
statements
END LOOP [ label
];
LOOP
定義一個(gè)無(wú)條件的循環(huán),它會(huì)無(wú)限重復(fù)直到被EXIT
或RETURN
語(yǔ)句終止??蛇x的label
可以被EXIT
和CONTINUE
語(yǔ)句用在嵌套循環(huán)中指定這些語(yǔ)句引用的是哪一層循環(huán)。
EXIT
EXIT [ label
] [ WHEN boolean-expression
];
如果沒(méi)有給出label
,那么最內(nèi)層的循環(huán)會(huì)被終止,然后跟在END LOOP
后面的語(yǔ)句會(huì)被執(zhí)行。如果給出了label
,那么它必須是當(dāng)前或者更高層的嵌套循環(huán)或者語(yǔ)句塊的標(biāo)簽。然后該命名循環(huán)或塊就會(huì)被終止,并且控制會(huì)轉(zhuǎn)移到該循環(huán)/塊相應(yīng)的END
之后的語(yǔ)句上。
如果指定了WHEN
,只有boolean-expression
為真時(shí)才會(huì)發(fā)生循環(huán)退出。否則,控制會(huì)轉(zhuǎn)移到EXIT
之后的語(yǔ)句。
EXIT
可以被用在所有類型的循環(huán)中,它并不限于在無(wú)條件循環(huán)中使用。
在和BEGIN
塊一起使用時(shí),EXIT
會(huì)把控制交給塊結(jié)束后的下一個(gè)語(yǔ)句。需要注意的是,一個(gè)標(biāo)簽必須被用于這個(gè)目的;一個(gè)沒(méi)有被標(biāo)記的EXIT
永遠(yuǎn)無(wú)法被認(rèn)為與一個(gè)BEGIN
塊匹配(這種狀況從PostgreSQL 8.4
之前的發(fā)布就已經(jīng)開(kāi)始改變。這可能允許一個(gè)未被標(biāo)記的EXIT
匹配一個(gè)BEGIN
塊)。
例子:
LOOP
-- 一些計(jì)算
IF count > 0 THEN
EXIT; -- 退出循環(huán)
END IF;
END LOOP;
LOOP
-- 一些計(jì)算
EXIT WHEN count > 0; -- 和前一個(gè)例子相同的結(jié)果
END LOOP;
<<ablock>>
BEGIN
-- 一些計(jì)算
IF stocks > 100000 THEN
EXIT ablock; -- 導(dǎo)致從 BEGIN 塊中退出
END IF;
-- 當(dāng)stocks > 100000時(shí),這里的計(jì)算將被跳過(guò)
END;
CONTINUE
CONTINUE [ label
] [ WHEN boolean-expression
];
如果沒(méi)有給出label
,最內(nèi)層循環(huán)的下一次迭代會(huì)開(kāi)始。也就是,循環(huán)體中剩余的所有語(yǔ)句將被跳過(guò),并且控制會(huì)返回到循環(huán)控制表達(dá)式(如果有)來(lái)決定是否需要另一次循環(huán)迭代。如果label
存在,它指定應(yīng)該繼續(xù)執(zhí)行的循環(huán)的標(biāo)簽。
如果指定了WHEN
,該循環(huán)的下一次迭代只有在boolean-expression
為真時(shí)才會(huì)開(kāi)始。否則,控制會(huì)傳遞給CONTINUE
后面的語(yǔ)句。
CONTINUE
可以被用在所有類型的循環(huán)中,它并不限于在無(wú)條件循環(huán)中使用。
例子:
LOOP
-- 一些計(jì)算
EXIT WHEN count > 100;
CONTINUE WHEN count < 50;
-- 一些用于 count IN [50 .. 100] 的計(jì)算
END LOOP;
WHILE
[ <<label
>> ]
WHILE boolean-expression
LOOP
statements
END LOOP [ label
];
只要boolean-expression
被計(jì)算為真,WHILE
語(yǔ)句就會(huì)重復(fù)一個(gè)語(yǔ)句序列。在每次進(jìn)入到循環(huán)體之前都會(huì)檢查該表達(dá)式。
例如:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- 這里是一些計(jì)算
END LOOP;
WHILE NOT done LOOP
-- 這里是一些計(jì)算
END LOOP;
FOR
(整型變體)[ <<label
>> ]
FOR name
IN [ REVERSE ] expression
.. expression
[ BY expression
] LOOP
statements
END LOOP [ label
];
這種形式的FOR
會(huì)創(chuàng)建一個(gè)在一個(gè)整數(shù)范圍上迭代的循環(huán)。變量name
會(huì)自動(dòng)定義為類型integer
并且只在循環(huán)內(nèi)存在(任何該變量名的現(xiàn)有定義在此循環(huán)內(nèi)都將被忽略)。給出范圍上下界的兩個(gè)表達(dá)式在進(jìn)入循環(huán)的時(shí)候計(jì)算一次。如果沒(méi)有指定BY
子句,迭代步長(zhǎng)為
1,否則步長(zhǎng)是BY
中指定的值,該值也只在循環(huán)進(jìn)入時(shí)計(jì)算一次。如果指定了REVERSE
,那么在每次迭代后步長(zhǎng)值會(huì)被減除而不是增加。
整數(shù)FOR
循環(huán)的一些例子:
FOR i IN 1..10 LOOP
-- 我在循環(huán)中將取值 1,2,3,4,5,6,7,8,9,10
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- 我在循環(huán)中將取值 10,9,8,7,6,5,4,3,2,1
END LOOP;
FOR i IN REVERSE 10..1 BY 2 LOOP
-- 我在循環(huán)中將取值 10,8,6,4,2
END LOOP;
如果下界大于上界(或者在REVERSE
情況下是小于),循環(huán)體根本不會(huì)被執(zhí)行。而且不會(huì)拋出任何錯(cuò)誤。
如果一個(gè)label
被附加到FOR
循環(huán),那么整數(shù)循環(huán)變量可以用一個(gè)使用那個(gè)label
的限定名引用。
使用一種不同類型的FOR
循環(huán),你可以通過(guò)一個(gè)查詢的結(jié)果進(jìn)行迭代并且操縱相應(yīng)的數(shù)據(jù)。語(yǔ)法是:
[ <<label
>> ]
FOR target
IN query
LOOP
statements
END LOOP [ label
];
target
是一個(gè)記錄變量、行變量或者逗號(hào)分隔的標(biāo)量變量列表。target
被連續(xù)不斷被賦予來(lái)自query
的每一行,并且循環(huán)體將為每一行執(zhí)行一次。下面是一個(gè)例子:
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;
如果循環(huán)被一個(gè)EXIT
語(yǔ)句終止,那么在循環(huán)之后你仍然可以訪問(wèn)最后被賦予的行值。
在這類FOR
語(yǔ)句中使用的query
可以是任何返回行給調(diào)用者的 SQL 命令:最常見(jiàn)的是SELECT
,但你也可以使用帶有RETURNING
子句的INSERT
、UPDATE
或
DELETE
。一些EXPLAIN
之類的功能性命令也可以用在這里。
PL/pgSQL變量會(huì)被替換到查詢文本中,并且如第 42.11.1 節(jié)和第 42.11.2 節(jié)中詳細(xì)討論的,查詢計(jì)劃會(huì)被緩存以用于可能的重用。
FOR-IN-EXECUTE
語(yǔ)句是在行上迭代的另一種方式:
[ <<label
>> ]
FOR target
IN EXECUTE text_expression
[ USING expression
[, ... ] ] LOOP
statements
END LOOP [ label
];
這個(gè)例子類似前面的形式,只不過(guò)源查詢被指定為一個(gè)字符串表達(dá)式,在每次進(jìn)入FOR
循環(huán)時(shí)都會(huì)計(jì)算它并且重新規(guī)劃。這允許程序員在一個(gè)預(yù)先規(guī)劃好了的命令的速度和一個(gè)動(dòng)態(tài)命令的靈活性之間進(jìn)行選擇,就像一個(gè)純EXECUTE
語(yǔ)句那樣。在使用EXECUTE
時(shí),可以通過(guò)USING
將參數(shù)值插入到動(dòng)態(tài)命令中。
另一種指定要對(duì)其結(jié)果迭代的查詢的方式是將它聲明為一個(gè)游標(biāo)。這會(huì)在第 42.7.4 節(jié)中描述。
FOREACH
循環(huán)很像一個(gè)FOR
循環(huán),但不是通過(guò)一個(gè) SQL 查詢返回的行進(jìn)行迭代,它通過(guò)一個(gè)數(shù)組值的元素來(lái)迭代(通常,FOREACH
意味著通過(guò)一個(gè)組合值表達(dá)式的部件迭代;用于通過(guò)除數(shù)組之外組合類型進(jìn)行循環(huán)的變體可能會(huì)在未來(lái)被加入)。在一個(gè)數(shù)組上循環(huán)的FOREACH
語(yǔ)句是:
[ <<label
>> ] FOREACH target
[ SLICE number
] IN ARRAY
expression
LOOP statements
END LOOP [ label
];
如果沒(méi)有SLICE
,或者如果沒(méi)有指定SLICE 0
,循環(huán)會(huì)通過(guò)計(jì)算expression
得到的數(shù)組的個(gè)體元素進(jìn)行迭代。target
變量被逐一賦予每一個(gè)元素值,并且循環(huán)體會(huì)為每一個(gè)元素執(zhí)行。這里是一個(gè)通過(guò)整數(shù)數(shù)組的元素循環(huán)的例子:
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;
元素會(huì)被按照存儲(chǔ)順序訪問(wèn),而不管數(shù)組的維度數(shù)。盡管target
通常只是一個(gè)單一變量,當(dāng)通過(guò)一個(gè)組合值(記錄)的數(shù)組循環(huán)時(shí),它可以是一個(gè)變量列表。在那種情況下,對(duì)每一個(gè)數(shù)組元素,變量會(huì)被從組合值的連續(xù)列賦值。
通過(guò)一個(gè)正SLICE
值,FOREACH
通過(guò)數(shù)組的切片而不是單一元素迭代。SLICE
值必須是一個(gè)不大于數(shù)組維度數(shù)的整數(shù)常量。target
變量必須是一個(gè)數(shù)組,并且它接收數(shù)組值的連續(xù)切片,其中每一個(gè)切片都有SLICE
指定的維度數(shù)。這里是一個(gè)通過(guò)一維切片迭代的例子:
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}
默認(rèn)情況下,PL/pgSQL函數(shù)中發(fā)生的任何錯(cuò)誤都會(huì)中止函數(shù)和周圍事務(wù)的執(zhí)行。你可以使用一個(gè)帶有EXCEPTION
子句的BEGIN
塊俘獲錯(cuò)誤并且從中恢復(fù)。其語(yǔ)法是BEGIN
塊通常的語(yǔ)法的一個(gè)擴(kuò)展:
[ <<label
>> ]
[ DECLARE
declarations
]
BEGIN
statements
EXCEPTION
WHEN condition
[ OR condition
... ] THEN
handler_statements
[ WHEN condition
[ OR condition
... ] THEN
handler_statements
... ]
END;
如果沒(méi)有發(fā)生錯(cuò)誤,這種形式的塊只是簡(jiǎn)單地執(zhí)行所有statements
, 并且接著控制轉(zhuǎn)到END
之后的下一個(gè)語(yǔ)句。但是如果在statements
內(nèi)發(fā)生了一個(gè)錯(cuò)誤,則會(huì)放棄對(duì)statements
的進(jìn)一步處理,然后控制會(huì)轉(zhuǎn)到
EXCEPTION
列表。系統(tǒng)會(huì)在列表中尋找匹配所發(fā)生錯(cuò)誤的第一個(gè)condition
。如果找到一個(gè)匹配,則執(zhí)行對(duì)應(yīng)的handler_statements
,并且接著把控制轉(zhuǎn)到END
之后的下一個(gè)語(yǔ)句。如果沒(méi)有找到匹配,該錯(cuò)誤就會(huì)傳播出去,就好像根本沒(méi)有EXCEPTION
一樣:錯(cuò)誤可以被一個(gè)帶有
EXCEPTION
的閉合塊捕捉,如果沒(méi)有EXCEPTION
則中止該函數(shù)的處理。
condition
的名字可以是附錄 A中顯示的任何名字。一個(gè)分類名匹配其中所有的錯(cuò)誤。特殊的條件名OTHERS
匹配除了QUERY_CANCELED
和
ASSERT_FAILURE
之外的所有錯(cuò)誤類型(雖然通常并不明智,還是可以用名字捕獲這兩種錯(cuò)誤類型)。條件名是大小寫無(wú)關(guān)的。一個(gè)錯(cuò)誤條件也可以通過(guò)SQLSTATE
代碼指定,例如以下是等價(jià)的:
WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...
如果在選中的handler_statements
內(nèi)發(fā)生了新的錯(cuò)誤,那么它不能被這個(gè)EXCEPTION
子句捕獲,而是被傳播出去。一個(gè)外層的EXCEPTION
子句可以捕獲它。
當(dāng)一個(gè)錯(cuò)誤被EXCEPTION
捕獲時(shí),PL/pgSQL函數(shù)的局部變量會(huì)保持錯(cuò)誤發(fā)生時(shí)的值,但是該塊中所有對(duì)持久數(shù)據(jù)庫(kù)狀態(tài)的改變都會(huì)被回滾。例如,考慮這個(gè)片段:
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;
當(dāng)控制到達(dá)對(duì)y
賦值的地方時(shí),它會(huì)帶著一個(gè)division_by_zero
錯(cuò)誤失敗。這個(gè)錯(cuò)誤將被EXCEPTION
子句捕獲。而在RETURN
語(yǔ)句中返回的值將是x
增加過(guò)后的值。但是UPDATE
命令的效果將已經(jīng)被回滾。不過(guò),在該塊之前的
INSERT
將不會(huì)被回滾,因此最終的結(jié)果是數(shù)據(jù)庫(kù)包含Tom Jones
但不包含Joe Jones
。
進(jìn)入和退出一個(gè)包含EXCEPTION
子句的塊要比不包含EXCEPTION
的塊開(kāi)銷大的多。因此,只在必要的時(shí)候使用EXCEPTION
。
例 42.2. UPDATE
/INSERT
的異常
這個(gè)例子使用異常處理來(lái)酌情執(zhí)行UPDATE
或 INSERT
。我們推薦應(yīng)用使用帶有 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 -- 首先嘗試更新見(jiàn) UPDATE db SET b = data WHERE a = key; IF found THEN RETURN; END IF; -- 不在這里,那么嘗試插入該鍵 -- 如果其他某人并發(fā)地插入同一個(gè)鍵, -- 我們可能得到一個(gè)唯一鍵失敗
BEGIN INSERT INTO db(a,b) VALUES (key, data); RETURN; EXCEPTION WHEN unique_violation THEN -- 什么也不做,并且循環(huán)再次嘗試 UPDATE END; END LOOP; END; $$ LANGUAGE plpgsql; SELECT merge_db(1, 'david'); SELECT merge_db(1, 'dennis');
這段代碼假定unique_violation
錯(cuò)誤是INSERT
造成,并且不是由該表上一個(gè)觸發(fā)器函數(shù)中的INSERT
導(dǎo)致。如果在該表上有多于一個(gè)唯一索引,也可能會(huì)發(fā)生不正確的行為,因?yàn)椴还苣膫€(gè)索引導(dǎo)致該錯(cuò)誤它都將重試該操作。通過(guò)接下來(lái)要討論的特性來(lái)檢查被捕獲的錯(cuò)誤是否為所預(yù)期的會(huì)更安全。
異常處理器經(jīng)常被用來(lái)標(biāo)識(shí)發(fā)生的特定錯(cuò)誤。有兩種方法來(lái)得到PL/pgSQL中當(dāng)前異常的信息:特殊變量和GET STACKED DIAGNOSTICS
命令。
在一個(gè)異常處理器內(nèi),特殊變量SQLSTATE
包含了對(duì)應(yīng)于被拋出異常的錯(cuò)誤代碼(可能的錯(cuò)誤代碼列表見(jiàn)表 A.1)。特殊變量SQLERRM
包含與該異常相關(guān)的錯(cuò)誤消息。這些變量在異常處理器外是未定義的。
在一個(gè)異常處理器內(nèi),我們也可以用GET STACKED DIAGNOSTICS
命令檢索有關(guān)當(dāng)前異常的信息,該命令的形式為:
GET STACKED DIAGNOSTICS variable
{ = | := } item
[ , ... ];
每個(gè)item
是一個(gè)關(guān)鍵詞,它標(biāo)識(shí)一個(gè)被賦予給指定變量
(應(yīng)該具有接收該值的正確數(shù)據(jù)類型)的狀態(tài)值。表 42.2中顯示了當(dāng)前可用的狀態(tài)項(xiàng)。
表 42.2. 錯(cuò)誤診斷項(xiàng)
名稱 | 類型 | 描述 |
---|---|---|
RETURNED_SQLSTATE
|
text
|
該異常的 SQLSTATE 錯(cuò)誤代碼 |
COLUMN_NAME
|
text
|
與異常相關(guān)的列名 |
CONSTRAINT_NAME
|
text
|
與異常相關(guān)的約束名 |
PG_DATATYPE_NAME
|
text
|
與異常相關(guān)的數(shù)據(jù)類型名 |
MESSAGE_TEXT
|
text
|
該異常的主要消息的文本 |
TABLE_NAME
|
text
|
與異常相關(guān)的表名 |
SCHEMA_NAME
|
text
|
與異常相關(guān)的模式名 |
PG_EXCEPTION_DETAIL
|
text
|
該異常的詳細(xì)消息文本(如果有) |
PG_EXCEPTION_HINT
|
text
|
該異常的提示消息文本(如果有) |
PG_EXCEPTION_CONTEXT
|
text
|
描述產(chǎn)生異常時(shí)調(diào)用棧的文本行(見(jiàn)本文中的第 42.6.9 節(jié)) |
如果異常沒(méi)有為一個(gè)項(xiàng)設(shè)置值,將返回一個(gè)空字符串。
這里是一個(gè)例子:
DECLARE
text_var1 text;
text_var2 text;
text_var3 text;
BEGIN
-- 某些可能導(dǎo)致異常的處理
...
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
END;
GET DIAGNOSTICS
(之前在第 42.5.5 節(jié)中描述)命令檢索有關(guān)當(dāng)前執(zhí)行狀態(tài)的信息(反之上文討論的GET STACKED DIAGNOSTICS
命令會(huì)把有關(guān)執(zhí)行狀態(tài)的信息報(bào)告成一個(gè)以前的錯(cuò)誤)。它的
PG_CONTEXT
狀態(tài)項(xiàng)可用于標(biāo)識(shí)當(dāng)前執(zhí)行位置。狀態(tài)項(xiàng)PG_CONTEXT
將返回一個(gè)文本字符串,其中有描述該調(diào)用棧的多行文本。第一行會(huì)指向當(dāng)前函數(shù)以及當(dāng)前正在執(zhí)行GET DIAGNOSTICS
的命令。第二行及其后的行表示調(diào)用棧中更上層的調(diào)用函數(shù)。例如:
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
返回同類的棧跟蹤,但是它描述檢測(cè)到錯(cuò)誤的位置而不是當(dāng)前位置。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: