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.6. 基本语句
本节以及随后的一节里,描述所有 PL/pgSQL 明确可以理解的语句类型。任何无法识别为这样类型的语句将被做为 SQL 命令看待,并且被发送到主数据库引擎执行,正如在节37.6.2和节37.6.3中描述的那样。
37.6.1. 赋值
给一个变量或行/记录赋值用下面方法:
identifier
:=expression
;
如上所述,语句中的表达式是用一个发送到主数据库引擎的 SELECT
命令计算的。该表达式必须生成单一的数值。
如果表达式的结果数据类型和变量数据类型不一致,或者变量具有已知的尺寸/精度(比如 char(20)
),结果值将隐含地被 PL/pgSQL 解释器用结果类型的输出函数和变量类型的输入函数转换。注意,如果结果数值的字符串形式不是输入函数可以接受的形式,那么这样做可能导致类型输入函数产生的运行时错误。
例子:
user_id := 20; tax := subtotal * 0.06;
37.6.2. 执行一个没有结果的查询
对于不返回任何行的 SQL 命令,例如没有 RETURNING
子句的 INSERT
,你可以简单的在 PL/pgSQL 函数内写上该语句,然后执行该函数即可。
出现在查询文本中的任何 PL/pgSQL 变量名都会被参数符号代替,并在运行时将参数值替换为变量的当前值。这样就允许字面文本相同的查询在不同的调用中做不同的事情。
【注意】分成两个步骤之后就允许 PL/pgSQL 仅对查询进行一次规划并在之后的每次查询中重复使用这个规划。例如
DECLARE key TEXT; delta INTEGER; BEGIN ... UPDATE mytab SET val = val + delta WHERE id = key;上述查询文本在 SQL 引擎看来会是下面这个样子
UPDATE mytab SET val = val + $1 WHERE id = $2;虽然一般不需要考虑这些,但是它有助于理解语法错误信息。
警告 |
PL/pgSQL 将会替换所有匹配函数中已声明变量的标识符,这未必符合你的本意。因此,不要在函数中使用表名或字段名作为变量名,有时你可以通过在查询中使用限定名来解决这个问题: PL/pgSQL 将不会对限定名 |
有时评估一个表达式或 SELECT
查询但是丢弃其结果也是有用的,例如,调用一个具有副作用的函数,但对它的结果不感兴趣。要在 PL/pgSQL 中这样做,可以使用 PERFORM
语句:
PERFORM query
;
这将执行 query
并丢弃其结果。用 SELECT
命令重写 query
,并将 SELECT
替换为 PERFORM
,这样,PL/pgSQL 变量将会在查询中被照常替换。另外,如果查询生成至少一行结果的话,特殊变量 FOUND
将会被设为真,否则将被设为假。
【注意】有些人可能期望直接写
SELECT
就能同样达到此目的,但目前确实只有PERFORM
一种方法。诸如SELECT
这样返回行的查询将会被当作错误拒绝,除非其带有一个下面将要讨论的INTO
子句。
一个例子:
PERFORM create_mv('cs_session_page_requests_mv', my_query);
37.6.3. 执行一个仅有单行结果的查询
如果一个 SQL 命令的结果是一个单独的行(可能有多个字段),那么可以将其赋予一个记录变量、行类型变量、标量变量的列表。这可以通过在基本 SQL 命令之后添加一个 INTO
子句达到。例如
SELECTselect_expressions
INTO [STRICT]target
FROM ...; INSERT ... RETURNINGexpressions
INTO [STRICT]target
; UPDATE ... RETURNINGexpressions
INTO [STRICT]target
; DELETE ... RETURNINGexpressions
INTO [STRICT]target
;
这里的 target
可以是一个记录变量、行变量、逗号分隔的简单变量列表、逗号分隔记录/行字段列表。PL/pgSQL 变量将被照常代入查询的其余部分,适用于带有 RETURNING
的 SELECT
, INSERT
/UPDATE
/DELETE
,以及返回行集合的命令(比如 EXPLAIN
)。除 INTO
子句外,SQL 命令与其在 PL/pgSQL 外面时完全相同。
【提示】请注意,上面带有
INTO
的SELECT
和 PostgreSQL 普通的SELECT INTO
命令是不一样的,后者的INTO
目标是一个新创建的表。如果你想在 PL/pgSQL 函数里从一个SELECT
结果中创建表,那么请使用CREATE TABLE ... AS SELECT
语法。
如果将一行或者一个变量列表用做目标,那么查询的结果必需精确匹配目标的结构,否则就会产生运行时错误。如果目标是一个记录变量,那么它自动将自己配置成命令结果列的行类型。
INTO
子句几乎可以出现在 SQL 命令的任何地方。习惯上把它写在 SELECT
命令的 select_expressions
列表的之前或之后,对于其它命令则位于结尾。我们建议你遵守这个约定,以防万一 PL/pgSQL 分析器在未来的版本中变得更加严格。
如果没有指定 STRICT
,那么 target
将被设为查询返回结果的第一行或者 NULL(查询返回零行),请注意,除非用 ORDER BY
进行排序,否则"第一行"是不明确的。第一行之后的所有结果都将被丢弃。你可以检查特殊变量 FOUND
(参见 节37.6.6)来判断查询是否至少返回一行。
SELECT * INTO myrec FROM emp WHERE empname = myname; IF NOT FOUND THEN RAISE EXCEPTION 'employee % not found', myname; END IF;
如果指定了 STRICT
选项,那么查询必须返回恰好一行,否则将产生 NO_DATA_FOUND
(没有行) 或者 TOO_MANY_ROWS
(多于一行)运行时错误。可以使用异常块来捕获这些错误。例如:
BEGIN; SELECT * INTO STRICT myrec FROM emp WHERE empname = myname; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'employee % not found', myname; WHEN TOO_MANY_ROWS THEN RAISE EXCEPTION 'employee % not unique', myname; END;
成功执行了一个带有 STRICT
的命令之后,FOUND
将总是被设为真。
对于带有 RETURNING
的 INSERT
/UPDATE
/DELETE
,即使没有指定 STRICT
PL/pgSQL 也会在返回多行时报错。这是因为没有 ORDER BY
之类的选项用于确定究竟返回那一行。
【注意】
STRICT
兼容 Oracle PL/SQL 的SELECT INTO
行为以及相关语句。
对于如何处理一个 SQL 查询中返回的多行,参见节37.7.4。
37.6.4. 什么也不做
有时一个什么也不做的占位语句也是很有用的。例如,用于 if/then/else 的空分支。可以使用 NULL
语句达到这个目的。
NULL;
例如,下面的两段代码时相等的:
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN NULL; -- 忽略错误 END;
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN -- 忽略错误 END;
究竟使用哪一种取决于各人的喜好。
【注意】在 Oracle 的 PL/SQL 中,不允许出现空语句列,所以在这种情况下必须使用
NULL
语句,而 PL/pgSQL 允许你什么也不写。
37.6.5. 执行动态命令
你经常会希望在你的 PL/pgSQL 函数里生成动态命令。也就是那些每次执行的时候都会涉及不同表或不同数据类型的命令。在这样的情况下, PL/pgSQL 试图为命令缓冲执行计划的一般企图将不再合适。为了处理这样的问题,提供了 EXECUTE
语句:
EXECUTEcommand-string
[INTO [STRICT]target
];
这里的 command-string
是一个生成字符串(类型为 text
)的表达式,该字符串包含要执行的命令,而 target
是一个记录变量、行变量、逗号分隔的简单变量列表、逗号分隔的记录/行列表。
请特别注意在该命令字符串里将不会发生任何 PL/pgSQL 变量代换。变量的数值必需在构造命令字符串的时候插入该字符串。
和所有其它在 PL/pgSQL 里的命令不同,一个由 EXECUTE
语句运行的命令在服务器生命期内并不只准备和保存一次。相反,在该语句每次运行的时候,命令都准备一次。命令字符串可以在过程里动态地生成以便于对各种不同的表和字段进行操作。
INTO
子句声明 SQL 命令的结果应该传递到哪里。如果提供了一个行变量或者一个变量列表,那么它必须和查询生成的结果的结构一样(如果使用了记录变量,那么它回自动调整为匹配结果的结构)。如果返回了多行,那么只有第一行将被赋予 INTO
变量。如果返回零行,那么将给 INTO
变量赋予 NULL 。如果没有声明 INTO
子句,则抛弃查询结果。
如果使用了 STRICT
选项,那么在查询没有恰好返回一行的情况下将会报错。
目前 EXECUTE
里面尚不支持 SELECT INTO
。
使用动态命令的时候经常需要逃逸单引号。建议使用美元符界定函数体内的固定文本。如果你有没有使用美元符界定的老代码,请参考节37.2.1,这样在把老代码转换成更合理的结构时,会节省你的一些精力。
插入到构造出来的查询中的动态数值也需要特殊处理,因为他们自己可能包含引号字符。一个例子(除了特别说明之外,这里都假设你使用了美元符界定):
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = ' || quote_literal(newvalue) || ' WHERE key = ' || quote_literal(keyvalue);
这个例子显示了 quote_ident
和 quote_literal
函数的使用。为了安全,包含字段和表标识符的变量应该传递给 quote_ident
函数。那些包含数值的表达式,如果中的数值在构造出来的命令字符串里是文本字符串,那么应该传递给 quote_literal
。它们俩都会采取合适的步骤把输入文本包围在单或双引号里并且对任何嵌入其中的特殊字符进行合适的逃逸处理。
请注意美元符界定只对包围固定文本有用。如果想像下面这样做上面的例子,那就太糟糕了
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = $$' || newvalue || '$$ WHERE key = ' || quote_literal(keyvalue);
因为如果 newvalue
的内容碰巧含有 $$
,那么这段代码就有毛病了。同样的问题可能出现在你选用的任何美元符界定分隔符上。因此,要想安全地包围事先不知道地文本,就必须使用 quote_literal
。
动态命令和 EXECUTE
的一个更大的例子是例37-6,这个例子制作并执行了一个定义新函数的 CREATE FUNCTION
命令。
37.6.6. 获取结果状态
有好几种方法可以判断一条命令的效果。第一个方法是使用 GET DIAGNOSTICS
,它的形式如下:
GET DIAGNOSTICSvariable
=item
[, ...];
这条命令允许检索系统状态标识符。每个 item
是一个关键字,表示一个将要赋予该特定变量的状态值(该变量应该和要接收的数值类型相同)。当前可用的状态项有 ROW_COUNT
、最后一个 SQL 命令处理的行数量、RESULT_OID
、最后一条 SQL 命令插入的最后一行的 OID 。请注意 RESULT_OID
只有在一个向包含 OID 的表中 INSERT
的命令之后才有用。
一个例子:
GET DIAGNOSTICS integer_var = ROW_COUNT;
另外一个判断命令效果的方法是一个 boolean
类型的特殊变量 FOUND
,它在每个 PL/pgSQL 函数的开始都为假。并被下列语句设置:
一个
SELECT INTO
语句如果返回一行则将FOUND
设置为真,如果没有返回行则设置为假。一个
PERFORM
语句如果生成(或抛弃)一行,则将FOUND
设置为真,如果没有生成行则为假。如果至少影响了一行,那么
UPDATE
,INSERT
,DELETE
语句设置FOUND
为真,如果没有行受影响则为假。一个
FETCH
语句如果返回行则设置FOUND
为真,如果不返回行则为假。一个
FOR
语句如果迭代了一次或多次,则设置FOUND
真,否则为假。这个规律适用于所有FOR
语句的三种变体(整数FOR
循环、记录集的FOR
循环、动态记录集FOR
循环)。只有在FOR
循环退出的时候才设置FOUND
;在循环执行的内部,FOUND
不被FOR
语句修改,但是在循环体里它可能被其它语句的执行而修改。
FOUND
是每个 PL/pgSQL 里的局部变量;任何对它的任何修改只影响当前的函数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论