[Oracle10G_R2]01.SQL 和 PL/SQL 特性
[Oracle10G_R2]01.SQL 和 PL/SQL 特性
透明数据加密和 XQuery支持是Oracle数据库10g第2版中与SQL相关的两个最突出的新特性,但还有其它新特性。
这一部分涉及:
● 透明数据加密
● XML 查询
● 增强的 COMMIT
● 错误事件记录子句
● WRAP 程序包
● 条件编译
● 无限制的 DBMS 输出
● XML 查询
● 增强的 COMMIT
● 错误事件记录子句
● WRAP 程序包
● 条件编译
● 无限制的 DBMS 输出
透明数据加密
对于加密,许多用户深感矛盾:他们既感兴趣,又因意识密钥管理的复杂性而感到慎重,如果处理不当,则会导致设置的效率低下。加密和解密值还会带来相关的性能开销,这使得大部分应用程序架构师不太乐于接受该过程。结果是,很多系统设计根本没有加密,只是构筑了强大的外围防护,如强大的口令和适当的授权方案。
但是,请想象一下如果整个服务器被盗了,甚至只是磁盘被盗,这些磁盘可以装配在具有相同操作系统的服务器上,然后其中的数据将被销毁殆尽。或者有一个的 DBA 品行不端,在日常业务活动中恶意突破了外围防护,然后将您所有重要的客户信息洗劫一空。在这两种情况下,如果所涉及的商业机构是在加利福尼亚州(可能不久之后在美国的其他州),它们在法律上有责任将安全漏洞的情况通知给所有受到影响的客户。
在上述罕见(但确是事实)的情况中,认证方案没有实际意义。这就是为什么对于那些将安全作为头等大事的机构而言,透明数据加密(TDE)是一个如此有用的特性;它支持加密,同时将密钥管理的复杂性交给数据库引擎来处理。同时,它允许DBA在不必实际看到数据的情况下管理数据库表。
在Oracle数据库10g第2版中使用TDE时,可以随时地对表中的一列或多列进行加密;只需将列定义为加密形式即可,不用编写代码。请记住,加密需要使用密钥和算法对输入值进行加密。TDE 为特定的表生成单独的密钥。这种方法方便了密钥管理却也更易被他们窃取,所以数据库提供了另一种密钥——万能密钥——它可以在数据库级别上设置。表密钥是利用万能密钥进行加密的,要获得表密钥就需要这个万能密钥。因此,对列进行解密时需要万能密钥和表密钥。(有关常规加密以及在 Oracle 中使用供应程序包的进一步探讨,请参见Oracle杂志专栏“
为您的数据资产加密”。)
万能密钥存储在数据库外一个称为“钱夹”的地方——默认位置在 $ORACLE_BASE/admin/$ORACLE_SID/wallet。在概念上,它类似于下图。
在配置 TDE 之后——或者更明确地说是配置了钱夹和万能密钥之后——您可以使用它来保护数据值。要为表的一列加密,需要使用以下SQL:
create table accounts
(
acc_no number not null,
first_name varchar2(30) not null,
last_name varchar2(30) not null,
SSN varchar2(9) ENCRYPT USING 'AES128',
acc_type varchar2(1) not null,
folio_id number ENCRYPT USING 'AES128',
sub_acc_type varchar2(30),
acc_open_dt date not null,
acc_mod_dt date,
acc_mgr_id number
)
(
acc_no number not null,
first_name varchar2(30) not null,
last_name varchar2(30) not null,
SSN varchar2(9) ENCRYPT USING 'AES128',
acc_type varchar2(1) not null,
folio_id number ENCRYPT USING 'AES128',
sub_acc_type varchar2(30),
acc_open_dt date not null,
acc_mod_dt date,
acc_mgr_id number
)
在这里,您在列 SSN 和 FOLIO_ID 上使用了 TDE,它们现在以加密方式存储在表本身。但是,当用户从表中选择时,她看到以明文表示的数据,因为在检索过程中已经完成了解密。如果磁盘被盗,则包含在表段中的信息仍然保持加密状态。盗窃者需要表密钥才能看到加密的值,但是要获得表密钥,他需要万能密钥,而万能密钥存储在外部,因此无法获得。
注意列 SSN 和 FOLIO_ID 后面的子句,这些子句指定 ENCRYPT 使用 128 位高级加密标准。
数据库拥有预先配置的钱夹。要设置钱夹口令,可使用命令:
alter system set encryption key authenticated BY "topSecret";
如果还未创建钱夹,该命令将先创建钱夹,然后将口令设置为“topSecret”(区分大小写)。然后您就可以开始在表的创建和更改期间将加密用于列定义。
为外部表加密
在以上示例中,我使用散列表为列加密。您还可以在外部表上使用 TDE。例如,如果您希望生成一个包含 ACCOUNTS 的数据的转储文件,以便发送到不同的地点,则可以使用简单的 ENCRYPT 子句。
create table account_ext
organization external
(
type oracle_datapump
default directory dump_dir
location ('accounts_1_ext.dmp',
'accounts_2_ext.dmp',
'accounts_3_ext.dmp',
'accounts_4_ext.dmp')
)
parallel 4
as
select
ACC_NO,
FIRST_NAME,
LAST_NAME,
SSN ENCRYPT IDENTIFIED BY "topSecret",
ACC_TYPE,
FOLIO_ID ENCRYPT IDENTIFIED BY "topSecret",
SUB_ACC_TYPE,
ACC_OPEN_DT,
ACC_MOD_DT
from accounts;
organization external
(
type oracle_datapump
default directory dump_dir
location ('accounts_1_ext.dmp',
'accounts_2_ext.dmp',
'accounts_3_ext.dmp',
'accounts_4_ext.dmp')
)
parallel 4
as
select
ACC_NO,
FIRST_NAME,
LAST_NAME,
SSN ENCRYPT IDENTIFIED BY "topSecret",
ACC_TYPE,
FOLIO_ID ENCRYPT IDENTIFIED BY "topSecret",
SUB_ACC_TYPE,
ACC_OPEN_DT,
ACC_MOD_DT
from accounts;
在文件 accounts_*_ext.dmp 中,SSN 和 FOLIO_ID 的值不会是明文,而是加密形式。如果您希望使用这些文件作为外部表,则必须提供 topSecret 作为口令以读取这些文件。
在这里您可以看到,TDE 是访问控制的理想补充(而不是替代)。
在这里您可以看到,TDE 是访问控制的理想补充(而不是替代)。
在 SQL 中查询 XML
长期以来,对于很多包含大量字符内容的应用程序的数据类型而言,XML 已成为事实上的标准。最近它也已成为其他应用程序的的存储方法,而不仅仅限于大量的内容。
Oracle 从 Oracle9i 数据库开始就提供 XML 与数据库的集成。在该版本中,您可以使用很多不同的方法来查询 XML 内容。在 Oracle 数据库 10g 第 2 版中,新的 XQuery 和 XMLTable 函数使查询 XML 内容变得更容易。(注意:本文将不全面讨论 XQuery 的规范,有关背景知识,请阅读 Oracle 杂志文章“XQuery:一种新的搜索方法”。)
XQuery
首先,让我们来看这两种方法中较简单的一种:XQuery。请看下面的示例:
SQL> xquery
2 for $var1 in (1,2,3,4,5,6,7,8,9)
3 let $var2 := $var1 + 1
4 where $var2 < 6
5 order by $var2 descending
6 return $var2
7 /
Result Sequence
------------------
5
4
3
2
2 for $var1 in (1,2,3,4,5,6,7,8,9)
3 let $var2 := $var1 + 1
4 where $var2 < 6
5 order by $var2 descending
6 return $var2
7 /
Result Sequence
------------------
5
4
3
2
新的 SQL 命令 xquery 表示一个 XQuery 命令。请注意该命令:新语法模仿了 FOR ...IN ... 内嵌视图,该视图是在 Oracle9i 数据库中推出的。
XQuery 的一般结构由缩略语 FLOWR(发音为“flower”)来描述,它代表 FOR、LET、ORDER BY、WHERE 和 RETURN。在以上的示例中,我们看到第 2 行定义了数据的来源,即从 1 到 9 的一系列数字。它可以是任何来源 — 一组标量值或者 XML 数据的一个元素,由 FOR 子句指定。该行还指定一个变量来存取这些值 (var1)。在第 3 行中,另一个变量 var2 拥有的值是 var1 加 1,由 LET 子句指定。
对于所有这些返回值,我们只关心 6 以下的值,这是由子句 WHERE 指定的。然后我们根据 var2 的值以降序方式对结果集排序,如第 6 行中的 ORDER BY 子句所示。最后,利用 RETURN 子句将值返回给用户。
如果将该语法与常规 SQL 语法相比较,则 RETURN、FOR、WHERE 和 ORDER BY 类似于 SELECT、FROM、WHERE 和 ORDER BY。LET 子句没有对应的 SQL 语句,但它可以在其他子句中指定。
让我们来看这种功能强大的新工具的一个实际应用示例。首先,创建一个表,用于保存与一个帐户持有者间的详细通信信息。
create table acc_comm_log
(
acc_no number,
comm_details xmltype
);
(
acc_no number,
comm_details xmltype
);
现在,向其中插入一些记录。
insert into acc_comm_log
values
(
1,
xmltype(
'<CommRecord>
<CommType>EMAIL</CommType>
<CommDate>3/11/2005</CommDate>
<CommText>Dear Mr Smith</CommText>
</CommRecord>')
)
/
values
(
1,
xmltype(
'<CommRecord>
<CommType>EMAIL</CommType>
<CommDate>3/11/2005</CommDate>
<CommText>Dear Mr Smith</CommText>
</CommRecord>')
)
/
insert into acc_comm_log
values
(
2,
xmltype(
'<CommRecord>
<CommType>LETTER</CommType>
<CommDate>3/12/2005</CommDate>
<CommText>Dear Mr Jackson</CommText>
</CommRecord>')
);
values
(
2,
xmltype(
'<CommRecord>
<CommType>LETTER</CommType>
<CommDate>3/12/2005</CommDate>
<CommText>Dear Mr Jackson</CommText>
</CommRecord>')
);
insert into acc_comm_log
values
(
3,
xmltype(
'<CommRecord>
<CommType>PHONE</CommType>
<CommDate>3/10/2005</CommDate>
<CommText>Dear Ms Potter</CommText>
</CommRecord>')
);
values
(
3,
xmltype(
'<CommRecord>
<CommType>PHONE</CommType>
<CommDate>3/10/2005</CommDate>
<CommText>Dear Ms Potter</CommText>
</CommRecord>')
);
现在您可以看到表中的记录:
SQL> l
1 select acc_no,
2 XMLQuery(
3 'for $i in /CommRecord
4 where $i/CommType != "EMAIL"
5 order by $i/CommType
6 return $i/CommDate'
7 passing by value COMM_DETAILS
8 returning content) XDetails
9 from acc_comm_log
10 /
ACC_NO XDETAILS
---------- ------------------------------
1
2 <CommDate>3/12/2005</CommDate>
3 <CommDate>3/10/2005</CommDate>
XMLTable
1 select acc_no,
2 XMLQuery(
3 'for $i in /CommRecord
4 where $i/CommType != "EMAIL"
5 order by $i/CommType
6 return $i/CommDate'
7 passing by value COMM_DETAILS
8 returning content) XDetails
9 from acc_comm_log
10 /
ACC_NO XDETAILS
---------- ------------------------------
1
2 <CommDate>3/12/2005</CommDate>
3 <CommDate>3/10/2005</CommDate>
XMLTable
另一个函数 XMLTable 用于类似的目的,但是它象常规的 SQL 查询一样返回列。以下是其运行情况。
1 select t.column_value
2 from acc_comm_log a,
3 xmltable (
4 'for $root in $date
5 where $root/CommRecord/CommType!="EMAIL"
6 return $root/CommRecord/CommDate/text()'
7 passing a.comm_details as "date"
8* ) t
SQL> /
COLUMN_VALUE
---------------------
3/12/2005
3/10/2005
2 from acc_comm_log a,
3 xmltable (
4 'for $root in $date
5 where $root/CommRecord/CommType!="EMAIL"
6 return $root/CommRecord/CommDate/text()'
7 passing a.comm_details as "date"
8* ) t
SQL> /
COLUMN_VALUE
---------------------
3/12/2005
3/10/2005
此示例演示了如何将常规的 SQL 语句用于 XML 查询所返回的 XML 表。查询按照非常结构化的 FLOWR 模式来指定命令。
XQuery 与 XMLTable 的对比
既然您已经了解了在常规 SQL 查询中使用 XML 的两种方法,就让我们来看这二种方法适用的情形。
第一种方法 XQuery 允许您获取 XMLType 形式的数据,在任何支持它的程序或应用程序中都可以将其作为 XML 来处理。在您所看到的示例中,帐户数据的结果输出是 XML 格式,而您可以使用任何工具(不必是关系型工具)来处理和显示这些数据。第二种方法 XMLTable 结合了常规 SQL 和 XML 的功能。帐户数据的结果输出不是 XML 格式,而是关系型数据。
注意两个案例中的源代码都是 XML,但是 XQuery 使用 XMLType 来表示 XML 格式的数据,而 XMLTable 将其表示为关系表,可以像常规表一样进行处理。这种功能非常适用于要输出表的现有程序,它引入了 XML 的特性。
XML 在预先不太了解确切的数据结构的场合中非常有用。在以上示例中,根据不同模式,通信记录也不相同。如果是电子邮件,则属性可能是接收方的电子邮件地址、回复地址、任何复本(cc:、bcc: 等等)、消息的文本等等。如果是电话呼叫,则属性是所呼叫的电话号码、号码的类型(家庭、工作、移动电话等等)、应答者、留下的语音邮件等等。如果您要设计一个包含所有可能的属性类型的表,则它会包括很多列,并且极其冗长,造成读取困难。但是,如果您只有一个 XMLType 列,则可以将所有内容填在那里,但仍然保持通信类型的独特属性。查询仍然可以使用简单的 SQL 接口,使应用程序的开发变得轻而易举。
有关 Oracle 的 XQuery 实施的更多信息,请访问 OTN 上的
Oracle XQuery 页。
增强的 COMMIT
当提交会话时,将刷新重做日志缓冲区,将其内容存储到磁盘上的联机重做日志中。此过程确保在对数据库进行恢复操作时,可以根据需要利用重做日志中回放事务处理。
但是有时您可能想对一部分受保证的恢复能力进行调整,以获得更好的性能。利用 Oracle 数据库 10g 第 2 版,您现在可以控制如何将重做流写入到联机日志文件。您可以在执行提交语句时控制这种行为,或者只需更改数据库的默认行为即可。
让我们来看提交语句是工作过程。在事务处理后,当执行 COMMIT 时,可以附带一个子句:
COMMIT WRITE <option>
其中 <option> 是影响重做流的部分。选项 WAIT 是默认行为。例如,您可以执行:
COMMIT WRITE WAIT;
此命令与 COMMIT 本身的效果相同。在重做流写入到联机重做日志文件之前,提交命令不会将控制权交还给用户。如果您不希望等待,则可以执行:
COMMIT WRITE NOWAIT;
这样,控制权立即返还给会话,甚至是在将重做流写入联机重做日志之前。
当执行提交命令时,日志写入器进程将重做流写入到联机重做日志。如果您正在进行一系列事务处理(如在批处理环境中),则可能不希望如此频繁地进行提交。当然,最好的操作过程是更改应用程序以减少提交数量;但这可能说起来容易做起来难。在这种情况下,您只需执行以下的提交语句:
COMMIT WRITE BATCH;
此命令将以批量方式将重做流写入到日志文件中,而不是每次提交都执行写操作。在频繁提交的环境中,您可以使用这种技术来减少日志缓冲区刷新。如果您希望立即写入日志缓冲区,则可以执行:
COMMIT WRITE IMMEDIATE;
如果您数据库默认使用某种特定的提交行为,则可以执行以下语句。
ALTER SYSTEM SET COMMIT_WRITE = NOWAIT;
此命令将使数据库默认使用这一行为。您还可以使其成为会话级默认行为:
ALTER SESSION SET COMMIT_WORK = NOWAIT;
对于任何参数,如果完成设置后,则该参数在系统级执行。如果有会话级的设置,则会话级设置优先,而最后如果 COMMIT 语句后面有子句,则该子句优先。
此选项不可用于分布式事务处理。
记录错误并继续运行:错误事件记录子句
假设您正试图将表 ACCOUNTS_NY 的记录插入到表 ACCOUNTS 中。表 ACCOUNTS 在 ACC_NO 列上有一个主键。可能 ACCOUNTS_NY 中的某些行与该主键冲突。尝试使用一个常规的插入语句:
SQL> insert into accounts
2 select * from accounts_ny;
insert into accounts
*
ERROR at line 1:
ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) violated
2 select * from accounts_ny;
insert into accounts
*
ERROR at line 1:
ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) violated
表 ACCOUNTS_NY 中的记录均未被装载。现在,将错误事件记录项打开,尝试同样的操作。首先,您需要创建一个表来保存由 DML 语句所拒绝的记录。调用该表 ERR_ACCOUNTS。
exec dbms_errlog.CREATE_ERROR_LOG ('ACCOUNTS','ERR_ACCOUNTS')
接下来,执行前面的语句,并加入事件记录子句。
SQL> insert into accounts
2 select * from accounts_ny
3 log errors into err_accounts
4 reject limit 200
5 /
2 select * from accounts_ny
3 log errors into err_accounts
4 reject limit 200
5 /
6 rows created.
注意,表 ACCOUNTS_NY 包含 10 行,但只有六行被插入;其他四行由于某种错误而被拒绝。要找出错误是什么,可查询 ERR_ACCOUNTS 表。
SQL> select ORA_ERR_NUMBER$, ORA_ERR_MESG$, ACC_NO
2 from err_accounts;
2 from err_accounts;
ORA_ERR_NUMBER$ ORA_ERR_MESG$ ACC_NO
--------------- -------------------------------------------------- ------
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 9997
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS)vi 9998
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 9999
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 10000
olated
--------------- -------------------------------------------------- ------
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 9997
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS)vi 9998
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 9999
olated
1 ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 10000
olated
请注意列 ORA_ERR_NUMBER$,它显示在 DML 语句执行期间所遇到的 Oracle 错误号,还有 ORA_ERR_MESG$,它显示错误消息。在本例中,您可以看到四条记录被丢弃是因为它们与主键约束 PK_ACCOUNTS 相冲突。该表还捕获表 ACCOUNTS 的所有列,包括列 ACC_NO。查看被拒绝的记录,注意这些帐号已经在表中存在,因此这些记录由于 ORA-00001 错误而被拒绝。如果没有错误事件记录子句,则整个语句将会出错,不会拒绝任务记录。通过这个子句,只有无效的记录被拒绝;其他所有记录均得以接受。
从源头保护代码:WRAP 程序包
PL/SQL 程序单元经常包含关于公司流程和商业秘密的非常敏感和机密的信息,这使得它们与表相类似,成为受保护的实体组。为防止未经授权而查看源代码的情况,我们要经常使用 wrap 命令行实用程序,这将使程序变得很混乱。
只有在创建 PL/SQL 脚本后才能调用 wrap;该实用程序将输入的明文打包为一个文件。但是,在某些情况下,您可能希望在 PL/SQL 代码中动态生成包装。在这种情况下,因为还不存在源文件,不能调用 wrap 实用程序。
由于 Oracle 数据库 10g 第 2 版提供了一个供应程序包,您可以使用它创建代码,并进行打包。该程序包补充(而不是替代)了 wrap 实用程序。而后者仍然适合于希望使用命令行来快速打包大量源文件的情况。
例如,假设您希望以打包形式创建简单的过程 p1。
create or replace procedure p1 as
begin
null;
end;
begin
null;
end;
在 PL/SQL 单元中,您可以使用以下命令以打包方式动态地创建这一过程:
begin
dbms_ddl.create_wrapped
('create or replace procedure p1 as begin null; end;')
end;
/
dbms_ddl.create_wrapped
('create or replace procedure p1 as begin null; end;')
end;
/
现在您希望确认打包过程。您可以从字典中选择源文本。
SQL> select text from user_source where name = 'P1';
Text
-----------------------------------------------------------------
procedure p1 wrapped
a000000
369
abcd
abcd
-----------------------------------------------------------------
procedure p1 wrapped
a000000
369
abcd
abcd
... and so on ...
第一行 procedure p1 wrapped 是确认以打包方式创建过程。如果您利用 DBMS_METADATA.GET_DDL() 函数来获取该过程的 DDL,则仍然会看到源代码已被打包。
有时您可能会有略微不同的需求;例如,您可能要生成 PL/SQL 代码,但不想创建过程。在这种情况下,您可以将其保存在一个文件或表中,以便以后执行。但是因为以上方法创建了过程,所以该方法在这里行不通。所以您需要在程序包中调用另一个函数:
SQL> select dbms_ddl.wrap
2 ('create or replace procedure p1 as begin null; end;')
3 from dual
4 /
2 ('create or replace procedure p1 as begin null; end;')
3 from dual
4 /
DBMS_DDL.WRAP('CREATEORREPLACEPROCEDUREP1ASBEGINNULL;END;')
----------------------------------------------------------------------
create or replace procedure p1 wrapped
a000000
369
abcd
abcd
... and so on ...
----------------------------------------------------------------------
create or replace procedure p1 wrapped
a000000
369
abcd
abcd
... and so on ...
WRAP 函数的输出是一个可传递的参数,它代表着 PL/SQL 代码的打包输出结果。该参数可以保存在纯文件文件或表中,可以在以后执行。如果您生成的代码要在其他地方部署,并且必须要保证代码的安全性,则这种方法很有用。
如果您可以将所存储代码的全部文本作为一个 varchar2 数据类型(大小限制为 32K)来传递,则这一方法可以正常工作。如果 PL/SQL 代码超过 32K,则您必须使用一种略微不同的方法:接受一个集合变量作为输入。
在这里您可以使用一个供应的数据类型:程序包 DBMS_SQL 中的 varchar2。这是一个集合数据类型 (TABLE OF VARCHAR2),表的每个单元都接收多达 32K 的文本;可随意增加该表所含的单元数,以满足您的需要。例如,假设您必须包装一个名为 myproc 的非常长的过程,其定义如下:
create or replace procedure myproc as
l_key VARCHAR2(200);
begin
l_key := 'ARUPNANDA';
end;
l_key VARCHAR2(200);
begin
l_key := 'ARUPNANDA';
end;
当然,这根本不是一个非常长的过程;但是为了示范起见,假设它很长。为了将其创建为打包形式,您要执行以下的 PL/SQL 块:
1 declare
2 l_input_code dbms_sql.varchar2s;
3 begin
4 l_input_code (1) := 'Array to hold the MYPROC';
5 l_input_code (2) := 'create or replace procedure myproc as ';
6 l_input_code (3) := ' l_key VARCHAR2(200);';
7 l_input_code (4) := 'begin ';
8 l_input_code (5) := ' l_key := ''ARUPNANDA'';';
9 l_input_code (6) := 'end;';
10 l_input_code (7) := 'the end';
11 sys.dbms_ddl.create_wrapped (
12 ddl => l_input_code,
13 lb => 2,
14 ub => 6
15 );
16* end;
2 l_input_code dbms_sql.varchar2s;
3 begin
4 l_input_code (1) := 'Array to hold the MYPROC';
5 l_input_code (2) := 'create or replace procedure myproc as ';
6 l_input_code (3) := ' l_key VARCHAR2(200);';
7 l_input_code (4) := 'begin ';
8 l_input_code (5) := ' l_key := ''ARUPNANDA'';';
9 l_input_code (6) := 'end;';
10 l_input_code (7) := 'the end';
11 sys.dbms_ddl.create_wrapped (
12 ddl => l_input_code,
13 lb => 2,
14 ub => 6
15 );
16* end;
在这里我们定义了一个变量 l_input_code 来保存输入的明文代码。在第 4 行到第 10 行中,我们用要打包的代码来填充这些行。在本示例中,同样为了简单起见,我使用了非常短的行。实际上,您可能要使用非常长的行,其大小多达 32KB。同样,我在数组中只使用了 7 个单元;实际上您可能要使用若干单元来填充全部代码。
第 11 到第 15 行表明我如何调用该过程,以便将该过程创建为打包形式。在第 12 行中,我将集合作为一个参数 DDL 来传递。但是,在这里暂停一下 — 我已经分配了一个注释作为数组的第一个单元,可能用于文档。但它不是有效的语法。同样,我将另一个注释分配给数组的最后一个单元 (7),它也不是用于创建过程的有效语法。为了使包装操作仅仅处理有效的行,我在第 13 和第 14 行中指定了存储我们代码的集合的最低 (2) 和最高 (6) 的单元。参数 LB 表示数组的下界,在本示例中是 2,而 HB 是上界 (6)。
使用这种方法,现在可以从您的 PL/SQL 代码中以打包方式创建任意大小的过程。
PL/SQL 中的条件编译:一次编写,多次执行
你们中很多人曾经使用过 C 语言,它支持编译器指令的概念。在 C 程序中,根据相关编译器的版本,特定变量的值有可能不同。
在 Oracle 数据库 10g 第 2 版中,PL/SQL 有一个类似的特性:现在可以提供预处理器指令,它们在编译期间而不是在运行时进行求值。例如,让我们创建一个非常简单的返回字符串的函数。
1 create or replace function myfunc
2 return varchar2
3 as
4 begin
5 $if $$ppval $then
6 return 'PPVAL was TRUE';
7 $else
8 return 'PPVAL was FALSE';
9 $end
10* end;
2 return varchar2
3 as
4 begin
5 $if $$ppval $then
6 return 'PPVAL was TRUE';
7 $else
8 return 'PPVAL was FALSE';
9 $end
10* end;
注意第 5 行,您已经使用预处理器指令为变量 ppval 求值。因为 ppval 是一个预处理器变量,而不是常规的 PL/SQL 变量,所以使用 $$ 标志来指定它。同样,为了编译器能分辨自己只需在编译期间处理这些行,你要用特殊的 $ 标志来指定求值项,例如用 $if 代替 if。现在,利用变量 ppval 的不同值来编译这个函数。
SQL> alter session set plsql_ccflags = 'PPVAL:TRUE';
Session altered.
现在编译该函数并执行它。
SQL> alter function myfunc compile;
Function altered.
SQL> select myfunc from dual;
MYFUNC
-------------------------------------
PPVAL was TRUE
-------------------------------------
PPVAL was TRUE
在编译期间 ppval 的值被设为 false。现在更改该变量的值并重新执行该函数。
SQL> alter session set plsql_ccflags = 'PPVAL:FALSE';
Session altered.
SQL> select myfunc from dual;
MYFUNC
---------------------------------------------------------
PPVAL was TRUE
---------------------------------------------------------
PPVAL was TRUE
虽然这里 ppval 的值在会话中是 FALSE,但函数没有采用它;而是采用了在编译期间所设置的值。现在,重新编译该函数并执行它。
SQL> alter function myfunc compile;
Function altered.
SQL> select myfunc from dual;
MYFUNC
---------------------------------------------------
PPVAL was FALSE
---------------------------------------------------
PPVAL was FALSE
在编译期间,ppval 的值是 FALSE,而这就是所返回的值。
那么您如何利用这个特性呢?有几种可能性 — 例如,您可以将它用作一个调试标志来显示更多的消息,或者可以编写一个程序,这个程序在各个平台上进行不同的编译。因为求值是在编译期间而不是在运行时间内完成的,运行时效率得到显著增强。
当您拥有相同的预处理器标志(在所有将要编译的函数中引用该标志)时,以上示例运行正常。但是如果您的每段代码具有不同的标志,情况会怎样?例如,函数 calculate_interest 可能将标志 ACTIVE_STATUS_ONLY 设为 TRUE,而函数 apply_interest 可能将标志 FOREIGN_ACCOUNTS 设为 FALSE。为了利用相应的标志来编译这些函数,您可以执行:
alter function calculate_interest compile
plsql_ccflags = 'ACTIVE_STATUS_ONLY:TRUE'
reuse settings;
alter function apply_interest compile
plsql_ccflags = FOREIGN_ACCOUNTS:TRUE'
reuse settings;
plsql_ccflags = 'ACTIVE_STATUS_ONLY:TRUE'
reuse settings;
alter function apply_interest compile
plsql_ccflags = FOREIGN_ACCOUNTS:TRUE'
reuse settings;
注意,这些设置都不是会话级的。子句 reuse settings 确保在以后重新编译函数时使用相同的编译器指令。
让我们来看这个新特性的另一个变体。除了条件变量的定义之外,您还可以在条件编译中检查程序包的静态常量。例如,假设您希望基于一个布尔型打包常数来控制 PL/SQL 过程的调试输出。首先创建程序包
create or replace package debug_pkg
is
debug_flag constant boolean := FALSE;
end;
is
debug_flag constant boolean := FALSE;
end;
debug_flag 是在代码中确定条件逻辑的常数。现在您可以将代码嵌入程序包,如下所示:
create or replace procedure myproc
as
begin
$if debug_pkg.debug_flag $then
dbms_output.put_line ('Debug=T');
$else
dbms_output.put_line ('Debug=F');
$end
end;
as
begin
$if debug_pkg.debug_flag $then
dbms_output.put_line ('Debug=T');
$else
dbms_output.put_line ('Debug=F');
$end
end;
注意,打包的常量被直接引用,没有任何 $ 符号。在本案例中,不需要设置任何会话级或系统级的条件编译参数。在编译函数时,您也不需要传递任何额外的子句。要了解具体的工作过程,可执行:
SQL> exec myproc
Debug=F
因为现在 debug_pkg.debug_flag 的值是 FALSE,所以执行该过程返回了预期的“F”。现在,更改常数值:
create or replace package debug_pkg
is
debug_flag constant boolean := TRUE;
end;
is
debug_flag constant boolean := TRUE;
end;
然后再次执行该过程:
SQL> exec myproc
Debug=T
该过程获该常量的值,即预期的“T”。注意这里有一个非常重要的区别 — 您不需要重新编译过程;将自动获取对常量的更改!
无限制的 DBMS 输出
还记得类似以下各行的令人讨厌的错误吗?
ERROR at line 1:
ORA-20000:ORU-10027:buffer overflow, limit of 1000000 bytes
ORA-06512:at "SYS.DBMS_OUTPUT", line 32
ORA-06512:at "SYS.DBMS_OUTPUT", line 97
ORA-06512:at "SYS.DBMS_OUTPUT", line 112
ORA-06512:at line 2
ORA-20000:ORU-10027:buffer overflow, limit of 1000000 bytes
ORA-06512:at "SYS.DBMS_OUTPUT", line 32
ORA-06512:at "SYS.DBMS_OUTPUT", line 97
ORA-06512:at "SYS.DBMS_OUTPUT", line 112
ORA-06512:at line 2
这是由于供应程序包 dbms_output 过去能够处理的最大字符数量是 1 百万字节。在 Oracle 数据库 10g 第 2 版中,该限制已经解除:现在最大输出数量是不封顶的。您只需通过执行以下命令,就可以将其设为“unlimited”
set serveroutput on
以上语句的输出结果如下:
SQL> show serveroutput
serveroutput ON size 2000 format WORD_WRAPPED
serveroutput ON size 2000 format WORD_WRAPPED
注意输出的最大默认值过去是 2000 。在 Oracle 数据库 10g 第 2 版中,该命令显示以下结果:
SQL> show serveroutput
serveroutput ON SIZE UNLIMITED FORMAT WORD_WRAPPED
serveroutput ON SIZE UNLIMITED FORMAT WORD_WRAPPED
默认值是 UNLIMITED。
老版本的另一个不便之处是 dbms_output 所显示的行的最大长度。以下是行的长度超过 255 字节时的一个典型错误消息。
ERROR at line 1:
ORA-20000:ORU-10028:line length overflow, limit of 255 chars per line
ORA-06512:at "SYS.DBMS_OUTPUT", line 35
ORA-06512:at "SYS.DBMS_OUTPUT", line 115
ORA-06512:at line 2
ORA-20000:ORU-10028:line length overflow, limit of 255 chars per line
ORA-06512:at "SYS.DBMS_OUTPUT", line 35
ORA-06512:at "SYS.DBMS_OUTPUT", line 115
ORA-06512:at line 2
在 Oracle 数据库 10g 第 2 版中,行可以具有任意长度。