I. 教程
II. SQL 语言
III. 服务器管理
- 章14. 安装指导
- 章15. 在 Windows 上安装客户端
- 章16. 操作系统环境
- 章17. 服务器配置
- 章18. 数据库角色和权限
- 章19. 管理数据库
- 章21. 区域
- 章20. 用户认证
- 章22. 日常数据库维护工作
- 章23. 备份与恢复
- 章24. 高可用性与负载均衡
- 章25. 监控数据库的活动
- 章26. 监控磁盘使用情况
- 章27. 可靠性和预写式日志
- 章28. 回归测试
IV. 客户端接口
- Chapter 28. libpq - C 库
- 章29.
- Chapter 30. ecpg - 在 C 里嵌入 SQL
- Chapter 31. 信息模式
- 31.2. 数据类型
- 31.3. informationschemacatalog_name
- 31.4. applicable_roles
- 31.5. check_constraints
- 31.6. columndomainusage
- 31.7. column_privileges
- 31.8. columnudtusage
- 31.9. columns
- 31.10. constraintcolumnusage
- 31.11. constrainttableusage
- 31.12. datatypeprivileges
- 31.13. domain_constraints
- 31.14. domainudtusage
- 31.15. domains
- 31.16. element_types
- 31.17. enabled_roles
- 31.18. keycolumnusage
- 31.20. referential_constraints
- 31.21. rolecolumngrants
- 31.22. roleroutinegrants
- 31.23. roletablegrants
- 31.24. roleusagegrants
- 31.25. routine_privileges
- 31.26. routines
- 31.27. schemata
- 31.28. sql_features
- 31.29. sqlimplementationinfo
- 31.30. sql_languages
- 31.31. sql_packages
- 31.32. sql_sizing
- 31.33. sqlsizingprofiles
- 31.34. table_constraints
- 31.35. table_privileges
- 31.36. tables
- 31.37. triggers
- 31.38. usage_privileges
- 31.39. viewcolumnusage
- 31.40. viewtableusage
- 31.41. views
V. 服务器端编程
- 章33. 扩展 SQL
- 章34. 触发器
- 章35. 规则系统
- 章36. 过程语言
- 章37. PL/pgSQL - SQL 过程语言
- 章38. PL/Tcl - Tcl 过程语言
- 章39. PL/Perl - Perl 过程语言
- 章40. PL/Python - Python 过程语言
- 章41. 服务器编程接口
VI. 参考手册
- I. SQL 命令
- ABORT
- ALTER AGGREGATE
- ALTER CONVERSION
- ALTER DATABASE
- ALTER DOMAIN
- ALTER FUNCTION
- ALTER GROUP
- ALTER INDEX
- ALTER LANGUAGE
- ALTER OPERATOR
- ALTER OPERATOR CLASS
- ALTER ROLE
- ALTER SCHEMA
- ALTER SEQUENCE
- ALTER TABLE
- ALTER TABLESPACE
- ALTER TRIGGER
- ALTER TYPE
- ALTER USER
- ANALYZE
- BEGIN
- CHECKPOINT
- CLOSE
- CLUSTER
- COMMENT
- COMMIT PREPARED
- COMMIT
- COPY
- CREATE AGGREGATE
- CREATE CAST
- CREATE CONSTRAINT TRIGGER
- CREATE CONVERSION
- CREATE DATABASE
- CREATE DOMAIN
- CREATE FUNCTION
- CREATE GROUP
- CREATE INDEX
- CREATE LANGUAGE
- CREATE OPERATOR CLASS
- CREATE OPERATOR
- CREATE ROLE
- CREATE RULE
- CREATE SCHEMA
- CREATE SEQUENCE
- CREATE TABLE
- CREATE TABLE AS
- CREATE TABLESPACE
- CREATE TRIGGER
- CREATE TYPE
- CREATE USER
- CREATE VIEW
- DEALLOCATE
- DECLARE
- DELETE
- DROP OWNED
- DROP AGGREGATE
- DROP CAST
- DROP CONVERSION
- DROP DATABASE
- DROP DOMAIN
- DROP FUNCTION
- DROP GROUP
- DROP INDEX
- DROP LANGUAGE
- DROP OPERATOR CLASS
- DROP OPERATOR
- DROP ROLE
- DROP RULE
- DROP SCHEMA
- DROP SEQUENCE
- DROP TABLE
- DROP TABLESPACE
- DROP TRIGGER
- DROP TYPE
- DROP USER
- DROP VIEW
- END
- EXECUTE
- EXPLAIN
- FETCH
- GRANT
- INSERT
- LISTEN
- LOAD
- LOCK
- MOVE
- NOTIFY
- PREPARE TRANSACTION
- PREPARE
- REASSIGN OWNED
- REINDEX
- RELEASE SAVEPOINT
- RESET
- REVOKE
- ROLLBACK PREPARED
- ROLLBACK TO SAVEPOINT
- ROLLBACK
- SAVEPOINT
- SELECT
- SELECT INTO
- SET
- SET CONSTRAINTS
- SET ROLE
- SET SESSION AUTHORIZATION
- SET TRANSACTION
- SHOW
- START TRANSACTION
- TRUNCATE
- UNLISTEN
- UPDATE
- VACUUM
- II. PostgreSQL 客户端应用程序
- III. PostgreSQL 服务器应用程序
VII. 内部
- 章42. PostgreSQL 内部概貌
- 章43. 系统表
- 43.1. 概述
- 43.2. pg_aggregate
- 43.3. pg_am
- 43.4. pg_amop
- 43.5. pg_amproc
- 43.6. pg_attrdef
- 43.7. pg_attribute
- 43.8. pg_authid
- 43.9. pgauthmembers
- 43.10. pg_autovacuum
- 43.11. pg_cast
- 43.12. pg_class
- 43.13. pg_constraint
- 43.14. pg_conversion
- 43.15. pg_database
- 43.16. pg_depend
- 43.17. pg_description
- 43.18. pg_index
- 43.19. pg_inherits
- 43.20. pg_language
- 43.21. pg_largeobject
- 43.22. pg_listener
- 43.23. pg_namespace
- 43.24. pg_opclass
- 43.25. pg_operator
- 43.26. pg_pltemplate
- 43.27. pg_proc
- 43.28. pg_rewrite
- 43.29. pg_shdepend
- 43.30. pg_shdescription
- 43.31. pg_statistic
- 43.32. pg_tablespace
- 43.33. pg_trigger
- 43.34. pg_type
- 43.35. 系统视图
- 43.36. pg_cursors
- 43.37. pg_group
- 43.38. pg_indexes
- 43.39. pg_locks
- 43.40. pgpreparedstatements
- 43.41. pgpreparedxacts
- 43.42. pg_roles
- 43.43. pg_rules
- 43.44. pg_settings
- 43.45. pg_shadow
- 43.46. pg_stats
- 43.47. pg_tables
- 43.48. pgtimezoneabbrevs
- 43.49. pgtimezonenames
- 43.50. pg_user
- 43.51. pg_views
- 章44. 前/后端协议
- 章45. PostgreSQL 编码约定
- 章46. 本地语言支持
- 章47. 书写一个过程语言处理器
- 章48. 基因查询优化器
- 章49. 索引访问方法接口定义
- 章50. GiST 索引
- 章51. GIN 索引
- 章52. 数据库物理存储
- 章53. BKI 后端接口
- 章54. 规划器如何使用统计信息
VIII. 附录
37.7. 控制结构
控制结构可能是 PL/pgSQL 中最有用的(以及最重要)的部分了。利用 PL/pgSQL 的控制结构,你可以以非常灵活而且强大的方法操纵 PostgreSQL 的数据。
37.7.1. 从函数返回
有两个命令可以用来从函数中返回数据:RETURN
和 RETURN NEXT
。
37.7.1.1. RETURN
RETURN expression
;
带表达式的 RETURN
用于终止函数并把 expression
的值返回给调用者。这种形式用于不返回集合的 PL/pgSQL 函数。
如果返回标量类型,那么可以使用任何表达式。表达式的类型将被自动转换成函数的返回类型,就像在赋值中描述的那样。要返回一个复合(行)数值,你必须写一个记录或者行变量的 expression
。
如果你声明函数带输出参数,那么就只需要写无表达式的 RETURN
。那么输出参数变量的当前值将被返回。
如果你声明函数返回 void
,那么一个 RETURN
语句可以用于提前退出函数;但是不要在 RETURN
后面写一个表达式。
一个函数的返回值不能是未定义。如果控制到达了函数最顶层的块而没有碰到一个 RETURN
语句,那么它就会发生一个错误。不过,这个限制不适用于带输出参数的函数以及那些返回 void
的函数。在这些例子里,如果顶层的块结束,则自动执行一个 RETURN
语句。
37.7.1.2. RETURN NEXT
RETURN NEXT expression
;
如果一个 PL/pgSQL 函数声明为返回 SETOF
,那么遵循的过程则略有不同。在这种情况下,要返回的独立项是在 sometype
RETURN NEXT
命令里声明的,然后最后有一个不带参数的 RETURN
命令用于告诉这个函数已经完成执行了。RETURN NEXT
可以用于标量和复合数据类型;对于复合类型,将返回一个完整的结果"表"。
RETURN NEXT
实际上并不从函数中返回,它只是简单地把表达式的值保存起来。然后执行继续执行 PL/pgSQL 函数里的下一条语句。随着后继的 RETURN NEXT
命令的执行,结果集就建立起来了。最后一个 RETURN
应该没有参数,它导致控制退出该函数(或者你可以简单地让控制到达函数的结尾)。
如果你声明函数带有输出参数,那么就只需要写不带表达式的 RETURN NEXT
。输出参数的当前值将被保存,用于最终返回。请注意如果有多个输出参数,比如声明函数为返回 SETOF record
或者是在只有一个类型为 sometype
的输出参数时声明为 SETOF
,这样才能创建一个带有输出参数的返回集合的函数。sometype
使用 RETURN NEXT
的函数应该按照下面的风格调用:
SELECT * FROM some_func();
也就是说,这个函数用做 FROM
子句里面的一个表数据源。
【注意】目前的 PL/pgSQL 的
RETURN NEXT
实现在从函数返回之前把整个结果集都保存起来,就像上面描述的那样。这意味着如果一个 PL/pgSQL 函数生成一个非常大的结果集,性能可能会很差:数据将被写到磁盘上以避免内存耗尽,但是函数在完成整个结果集的生成之前不会退出。将来的 PL/pgSQL 版本可能会允许用户定义没有这样限制的返回集合的函数。目前,数据开始向磁盘里写的时刻是由配置变量 work_mem 控制的。拥有足够内存的管理员如果想在内存里存储更大的结果集,则可以考虑把这个参数增大一些。
37.7.2. 条件
IF
语句让你可以根据某种条件执行命令。PL/pgSQL 有五种形式的 IF
:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF ... THEN ... ELSEIF ... THEN ... ELSE
37.7.2.1. IF-THEN
IFboolean-expression
THENstatements
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;
37.7.2.2. IF-THEN-ELSE
IFboolean-expression
THENstatements
ELSEstatements
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
IFboolean-expression
THENstatements
[ELSIFboolean-expression
THENstatements
[ELSIFboolean-expression
THENstatements
...]] [ELSEstatements
] 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 -- hmm, the only other possibility is that number is null result := 'NULL'; END IF;
37.7.2.5. IF-THEN-ELSEIF-ELSE
ELSEIF
是 ELSIF
的别名。
37.7.3. 简单循环
使用 LOOP
, EXIT
, CONTINUE
, WHILE
, FOR
语句,可以控制 PL/pgSQL 函数重复一系列命令。
37.7.3.1. LOOP
[<<label
>>] LOOPstatements
END LOOP [label
];
LOOP
定义一个无条件的循环,无限循环,直到由 EXIT
或 RETURN
语句终止。可选的 label
可以由 EXIT
和 CONTINUE
语句使用,用于在嵌套循环中声明应该应用于哪一层循环。
37.7.3.2. EXIT
EXIT [label
] [WHENexpression
];
如果没有给出 label
,那么退出最内层的循环,然后执行跟在 END LOOP
后面的语句。如果给出 label
,那么它必须是当前或者更高层的嵌套循环块或者语句块的标签。然后该命名块或者循环就会终止,而控制落到对应循环/块的 END
语句后面的语句上。
如果声明了 WHEN
,循环退出只有在 expression
为真的时候才发生,否则控制会落到 EXIT
后面的语句上。
EXIT
可以用于在所有的循环类型中,它并不仅仅限制于在无条件循环中使用。在和 BEGIN
块一起使用的时候,EXIT
把控制交给块结束后的下一个语句。
例子:
LOOP -- 一些计算 IF count > 0 THEN EXIT; -- 退出循环 END IF; END LOOP; LOOP -- 一些计算 EXIT WHEN count > 0; -- same result as previous example END LOOP; BEGIN -- 一些计算 IF stocks > 100000 THEN EXIT; -- 导致从 BEGIN 块里退出 END IF; END;
37.7.3.3. CONTINUE
CONTINUE [label
] [WHENexpression
];
如果没有给出 label
,那么就开始最内层循环的下一次执行。也就是说,控制传递回给循环控制表达式(如果有),然后重新计算循环体。如果出现了 label
,它声明即将继续执行的循环的标签。
如果声明了 WHEN
,那么循环的下一次执行只有在 expression
为真的情况下才进行。否则,控制传递给 CONTINUE
后面的语句。
CONTINUE
可以用于所有类型的循环;它并不仅仅限于无条件循环。
例子:
LOOP -- 一些计算 EXIT WHEN count > 100; CONTINUE WHEN count < 50; -- 一些在 count 数值在[50 .. 100]里面时候的计算 END LOOP;
37.7.3.4. WHILE
[<<label
>>] WHILEexpression
LOOPstatements
END LOOP [label
];
只要条件表达式为真,WHILE
语句就会不停的在一系列语句上进行循环,条件是在每次进入循环体的时候检查的。
For example:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP -- 一些计算 END LOOP; WHILE NOT boolean_expression LOOP -- 一些计算 END LOOP;
37.7.3.5. FOR
(integer variant)
[<<label
>>] FORname
IN [REVERSE]expression
..expression
[BYexpression
] LOOPstatements
END LOOP [label
];
这种形式的 FOR
对一定范围的整数进行迭代的循环。变量 name
会自动定义为 integer
类型并且只在循环里存在(任何该变量名的现存定义在此循环内都将被忽略)。给出范围上下界的两个表达式在进入循环的时候计算一次。BY
子句指定迭代步长(缺省为 1),但如果声明了 REVERSE
步长将变为相应的负值。
一些整数 FOR
循环的例子:
FOR i IN 1..10 LOOP -- 一些计算 RAISE NOTICE 'i is %', i; END LOOP; FOR i IN REVERSE 10..1 LOOP -- 一些计算 END LOOP; FOR i IN REVERSE 10..1 BY 2 LOOP -- 一些计算 RAISE NOTICE 'i is %', i; END LOOP;
如果下界大于上界(或者是在 REVERSE
情况下是小于),那么循环体将完全不被执行。而且不会抛出任何错误。
37.7.4. 遍历命令结果
使用不同类型的 FOR
循环,你可以遍历一个命令的结果并且对其进行相应的操作。语法是:
[<<label
>>] FORtarget
INquery
LOOPstatements
END LOOP [label
];
target
是一个记录变量、行变量、逗号分隔的标量变量列表。target
被连续不断被赋予所有来自 query
的行,并且循环体将为每行执行一次。下面是一个例子:
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(mviews.mv_name) || ' ' || mviews.mv_query; END LOOP; PERFORM cs_log('Done refreshing materialized views.'); RETURN 1; END; $$ LANGUAGE plpgsql;
如果循环是用一个 EXIT
语句终止的,那么在循环之后你仍然可以访问最后赋值的行。
query
可以是任何返回行的 SQL 命令,通常是 SELECT
,不过带有 RETURNING
子句的 INSERT
, UPDATE
, DELETE
也是可以的,一些诸如 EXPLAIN
之类的命令也可以。
FOR-IN-EXECUTE
语句是遍历所有行的另外一种方法:
[<<label
>>] FORtarget
IN EXECUTEtext_expression
LOOPstatements
END LOOP [label
];
这个例子类似前面的形式,只不过源查询语句声明为了一个字符串表达式,这样它在每次进入 FOR
循环的时候都会重新计算和生成执行计划。这样就允许程序员在一个预先规划好了的命令所获得的速度和一个动态命令所获得的灵活性(就像一个简单的 EXECUTE
语句那样)之间进行选择。
【注意】 PL/pgSQL 分析器目前区分两种类型的
FOR
循环(整数或者返回记录的)。方法是检查是否有任何..
出现在IN
和LOOP
之间的圆括弧之外。如果没有看到..
,那么这个循环就是在数据行上的循环。如果误敲了..
就很可能会导致类似"loop variable of loop over rows must be a record or row variable or list of scalar variables"这样的错误信息,而不是简单的语法错误。
37.7.5. 捕获错误
缺省时,一个在 PL/pgSQL 函数里发生的错误退出函数的执行,并且实际上其周围的事务也会退出。你可以使用一个带有 EXCEPTION
子句的 BEGIN
块捕获错误并且从中恢复。其语法是正常的 BEGIN
块语法的一个扩展:
[<<label
>>] [DECLAREdeclarations
] BEGINstatements
EXCEPTION WHENcondition
[ORcondition
...] THENhandler_statements
[WHENcondition
[ORcondition
...] THENhandler_statements
...] END;
如果没有发生错误,这种形式的块只是简单地执行所有 statements
,然后转到下一个 END
之后的语句。但是如果在 statements
内部发生了一个错误,则对 statements
的进一步处理将废弃,然后转到 EXCEPTION
列表。系统搜索这个列表,寻找匹配错误的第一个 condition
。如果找到匹配,则执行对应的 handler_statements
,然后转到 END
之后的下一个语句。如果没有找到匹配,该错误就会广播出去,就好像根本没有 EXCEPTION
子句一样:该错误可以被一个包围块用 EXCEPTION
捕获,如果没有包围块,则退出函数的处理。
condition
的名字可以是 附录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
的增量值。但是,在该块之前的 UPDATE
和 INSERT
将不会回滚,因此最终的结果是数据库包含 Tom Jones
而不是 Joe Jones
。
【提示】进入和退出一个包含
EXCEPTION
子句的块要比不包含的块开销大的多。因此,不必要的时候不要使用EXCEPTION
。
在异常处理器中,SQLSTATE
变量包含抛出错误对应的错误代码(参考表A-1获取可能的错误码的列表)。SQLERRM
变量包含与异常关联的错误信息。这些变量在异常处理器外面是未定义的。
例37-1. UPDATE
/INSERT
异常
这个例子根据使用异常处理器执行恰当的 UPDATE
或 INSERT
。
CREATE TABLE db (a INT PRIMARY KEY, b TEXT); CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS $$ 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 -- do nothing END; END LOOP; END; $$ LANGUAGE plpgsql; SELECT merge_db(1, 'david'); SELECT merge_db(1, 'dennis');
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论