分类: Oracle
2008-03-21 15:20:58
附加的字符串
作者:Steven Feuerstein
现在你可以通过字符串建立索引提高性能。
许多PL/SQL程序对数据进行操作,通常是通过使用SQL在数据库中直接操作数据。你经常还需要在PL/SQL程序本身内声明和管理数据。这个程序数据可能由一些单独的值(标量)组成。但是在其他许多情况下,你可能要处理更复杂的数据结构,从记录到对象(对象类型的实例)甚至是列表。
列表(以及以更复杂的方式表示的数组)是程序员工具箱中最重要的元素。为了创建和管理这些列表,PL/SQL提供了各种各样称作集合的结构,如:嵌套表,可变数组和关联数组。
在Oracle9i第2版中引入的关联数组替代了索引表(它在Oracle8中替代了PL/SQL表)。关联数组引入了重要的新功能。在Oracle9i第2版中,你现在可以:
定义在其内部包含其它集合的集合。这些被叫做多层集合的列表使你能够在PL/SQL数据结构内部更加直观和直接地对现实世界的情况建模。我在Oracle杂志2002年5/6月号的文章"多层次的编程"中介绍过这个特性。
我们可以创建通过字符串来建立索引行的集合(并可快速进行检索)。例如,公司的名字在该集合中可以是"行号"。
本篇文章说明怎样通过串来对关联数组建立索引和怎样定义这样的集合。
用字符串来建立索引
有一个新的方法可用来定义和操作PL/SQL特定的集合。特别是,你除了使用行号这样的整数值来建立索引以外,你还可以通过字符串来建立索引。这个方法提供了相当重要的额外灵活性。请看清单1中的例子。
在清单1中,第2行声明了一个通过最大长度为64个字符的串建立了索引的存储数字的关联数组类型。第3行和第4行基于这个类型声明了2个关联数组。第5行声明了一个变量来存储集合中的行号。第6行声明了一个变量来存储"极限值"(最低和最高的行索引值)。请注意,因为这个变量会接收一个基于串索引的值,所以它被声明为一个字符串。在第8行和第9行中,我将值赋给了country_population。每次我都把国家的人口数赋给集合中的一行。那一行的索引就是它的国名。在第11行中,我从集合中检索出一个值。请注意,我通过国名来确定那一行,而脚本将返回相应的人口数。从第18行到第20行,我得到第一个定义的行值,然后显示它,接着显示那个国家的人口数。数据库是怎样确定第一行的呢?第一行是通过数据库中字符集特定的排序顺序来确定的。从第22行到第24行,我使用对第一个定义的行值的相同的过程来得到最后一个定义的行值。
对于字符串索引的集合你可能需要慢慢地习惯,但是这种集合通过调用FIRST、LAST、PRIOR和NEXT方法得到的返回值是字符串而不是数。
字符串索引集合的使用
为什么你应该使用字符串而不是数来建立索引呢?假设你需要在你的程序中对员工的信息进行一些大量的处理。你可能需反复地处理一组选定的雇员的信息,例如,按雇员的ID号码、姓氏和社会保险号码(对于美国以外的一些国家或者是相应的身份证号码)来搜索员工。
当然你可以使用SQL来完成这些任务,但这远不是最高效的实现方式。如果你需要多次处理一组数量很大的静态数据,那么你可以改为把这组数据从数据库中移到一组集合中。访问基于集合的数据比使用SQL引擎来访问数据要快得多。
接下去你可以利用这些集合的基于字符串和基于整数的索引来模仿表(你已经把该表的数据从数据库中传出来了)的主键和特有的索引。这个方法的一个简单的例子已在清单2的代码中展示出。
在清单2中,第1行到第4行声明了两个关联数组类型。注意,我可以在INDEX BY子句中利用%TYPE,在其它的子句中使用PLS_INTEGER代替BINARY_INTEGER。这些都是Oracle9i Release 2的新功能。在第6行到第8行,我声明了将用来提供进入数据的多个快速入口点的集合。从第12行到第21行,load_arrays过程显示了从数据库表将数据传送到一个或多个(在该实例中是3个集合)是多么得容易。我通过使用不同的字段值作为关键字,把整行的雇员数据作为一个记录存放到每一个集合中。在第17行中,我把姓氏用作索引值。在第18行中,我使用社会安全号码作为索引值。在第19行中,主关键字作为索引值(它是一个把整数索引作为一个"智能关键字"的相当传统的应用)。在第27行中,我通过使用字符串和整数索引值对两个不同集合中的工资域进行比较。
用字符串作索引的多层次集合
我已经创建了一个有趣的新实用程序,叫做Codecheck,它利用了 Oracle9i第2版集合的一些增强特性。该程序包仔细审查ALL_ARGUMENT数据字典视图的内容,以便分析特定的程序包和单独的应用程序其代码是否符合编码标准或揭示一些设计问题(例如不明确的超负荷)。
ALL_ARGUMENTS包含关于每个存储在数据库(连接的用户在其上拥有EXECUTE权限中)的过程和函数的每个参数或自变量的信息。ALL_ARGUMENTS中的单一行包含关于一个自变量(或者,在某些情况下,一个自变量的一个域(field)或一个元素)的信息。下面是为ALL_ARGUMENT定义的字段的一个子集:
名字 空值? 类型
--------------- -------- ------------
OWNER 非空值 VARCHAR2(30)
OBJECT_NAME VARCHAR2(30)
PACKAGE_NAME VARCHAR2(30)
OVERLOAD VARCHAR2(40)
ARGUMENT_NAME VARCHAR2(30)
POSITION 非空值 NUMBER
DATA_LEVEL 非空值 NUMBER
DATA_TYPE VARCHAR2(30)
DEFAULT_VALUE LONG
IN_OUT VARCHAR2(9)
很快你就会看到嵌入在这些行中的层次:每个对象(OWNER.OBJECT_NAME)可能超载(overload)(如果不超载或是一个负数,则OVERLOAD是空值)。overloading中的每一个自变量都有一个位置,而在那个位置内你可以有自变量信息的多个"层次"。换句话说,
OBJECT_NAME
OVERLOAD
ARGUMENT_NAME
POSITION
LEVEL
由于篇幅所限我不可能提供Codecheck程序包中太多实用程序的实现。无论如何,我很高兴与您分享我为了轻松地快速分析ALL_ARGUMENTS的内容而把它的数据传送到我的集合中而写的代码。
我在Codecheck程序包上进行第一次传送中,我定义了一个可以简单地映射数据字典视图的集合类型和集合,如下所示:
CREATE OR REPLACE PACKAGE BODY Codecheck
IS
TYPE args_t IS TABLE OF
all_arguments%ROWTYPE
INDEX BY BINARY_INTEGER;
arguments args_t;
正如清单3所示,我通过使用定义的集合,只使用了很少量的代码它就可以为特定的程序获得数据并把它插入到我的集合中。
定义嵌套的集合
但是,我在写完清单3中的代码后马上意识到,我正在以过时的方式思考问题。我的计划是先填满我的集合接着写一些复杂的代码来扫描自变量集合,然后查找一些事情,诸如:
一个程序包中特有程序的代号和名字。我可能一共有12个程序,但由于超载因而只有3个不同的程序名。
顶级自变量项(其DATA_LEVEL = 0)。
这就是在集合中用于标识一个程序结束和另一个程序开始的那些自变量。
解决在ALL_ARGUMENTS视图中数据的组织问题并写出所需要的代码是我的任务。
假如我定义多个嵌套的集合来存储这个数据将会怎样呢?也许通过这个方法,集合的该组织可以使我更加容易地回答一些我的问题。在经过思考和测试了一些可选方案之后,我得到了如清单4所示的集合类型的层次结构。
图1:在ALL_ARGUMENTS集合映射中的四层嵌套
下面是对清单4内容的描述。像这样的情况最好是使用"自底向上",或者是从层次的最外层到最内部的集合的研究方法。在第15行中,我声明了类型为programs_t的集合。这个单独的集合将包含ALL_ARGUMENTS视图中的全部信息,正像我的第一次尝试中那样,但是该信息的组织却完全不同。在第12行和第13行中,我声明了集合类型programs_t。这个类型的集合中的每一行包含有关一个给定程序名的全部overloadings的自变量的所有相关信息。请注意,索引是对象的名字。在第9行和第10行中,我声明了集合类型overloadings_t。这个类型的集合中的每一行包含有关一个程序的一个单一overloading的自变量的所有相关信息。现在我再次使用一个整数索引,因为在这种情况下关键字是ALL_ARGUMENTS视图中的OVERLOAD字段的值。在第6行和第7行中,我声明了集合类型arguments_t。这个类型的集合中的每一行包含一个特定overloading的一个单一参数或自变量的所有相关信息。我还是使用一个整数索引,因为在该情况下,关键字是ALL_ARGUMENTS的POSITION字段的值(即参数列表中的位置)在第3行和第4行中,我声明了集合类型breakouts_t。这个类型的集合中的每一行包含一个自变量的一个单一元素的所有相关信息。这可能是一行也可能是多行数据。如果例如这个问题中的自变量是一个有15个域的记录,那么这个集合至少有15行。我借助一个整数索引,因为在这个例子中关键字是ALL_ARGUMENTS视图中的LEVEL字段的值(在参数列表中0表示实际的自变量)。
是否令人有些困惑?首先,不要只盯着ALL_ARGUMENTS视图中数据的复杂性。我只想给你一个在我的代码中需要操作的变量的感受。接下来我们看一下图1中的图表。注意在ALL_ARGUMENTS视图中嵌套的层次是怎样简单地以这些不同的层次来表达的。
有了g_programs集合之后,让我们再次查看如清单5所示的Codecheck包中的load_arguments过程。
清单3中的load_arguments过程和清单5中的版本有什么不同?整体地看非常小。唯一不同的语句是在清单5的第12行到第16行。下面是在清单3中的较早的简单赋值语句:
arguments (
NVL (arguments.LAST, 0) + 1)
:= rec;
在清单5中,它变成了:
g_programs
(rec.object_name)
(rec.overload)
(rec.position)
(rec.data_level) := rec;
这当然很复杂,但它会为我处理那可怕的复杂性。例如,通过使用这个单一的赋值语句,我实际上把全部4个集合中的所有行放到该层次结构中。当我完成这一工作后,只花了很少的代码就满足了我的检索数据的需要。例如,获得TOTAL_SALES函数的第2个overloading的RETURN子句的数据类型:
g_programs ('TOTAL_SALES') -- locate
(2) -- second overloading
(0) -- RETURN clause has position 0
(0) -- Top-level argument
.data_type -- Value in the
data_type field
在这种情况下,使用这个语法比编写和调试扫描一个"平面(flat)"(Oracle9i数据库之前)集合所需要的逻辑来得到这样的信息代码要容易得多。
当然我并不期望你能立即完全领会这些复杂的集合结构的潜在能力,我只希望这个段落的一些例子可以起到抛砖引玉的作用。
这么多的选择!
只要适当地使用,集合将帮助我们编写出更高效和更易懂的代码。将多层次的编程和用字符串建立索引可以帮助我们减少复杂性和减少建模和操作来自复杂数据库设计的信息所需的编码量。