Академический Документы
Профессиональный Документы
Культура Документы
SUMMARY OF ENHANCEMENTS
A number of important enhancements have been made to PL/SQL in Oracle9i in each of these areas: its
implementation (i.e. that which effects the execution characteristics of a given system of source code); language
features (i.e. the addition of new syntax to express powerful new semantics); and Oracle supplied PL/SQL library
units.
Some of the enhancements are transparent, for example: the change to use the same parser for compile-time checked
embedded SQL as is used for compiling SQL issued from other programming environments; or the reimplementation
of the Utl_Tcp package (moving from java to native C). No user action is required to enjoy these (beyond installing
Oracle9i).
Some are semi-transparent, for example the new option to compile PL/SQL source to native C. Small declarative steps
are required to enjoy these, while existing application code remains unchanged.
And some introduce new semantics, either in the language itself or by virtue of new APIs in the supplied PL/SQL library
units. Study and thought is required in order to design and implement changes to existing code or creation of new
code to leverage these enhancements.
The following enhancements have been selected for detailed description…
• Native compilation of PL/SQL
• CASE statements and CASE expressions
• Bulk binding enhancements:
exception handling with bulk binding;
bulk binding in native dynamic SQL
• Table functions and cursor expressions
• Multilevel collections
• Enhancements to the Utl_Http package
Extensive complete code samples are listed in the appendix to provide working demonstrations for all these features.
The remaining enhancements are: either completely transparent; or need no special explanation from a PL/SQL
perspective (since they are new SQL language features which PL/SQL supports in an obvious way); or are object-
oriented features which are scoped out of this paper; or are features of supplied packages and as such are less
interesting from a PL/SQL language viewpoint. These will be listed briefly in the last sections…
• Transparent enhancements
• New SQL features
• Object oriented features
• New or enhanced supplied packages
Fast Track to Oracle9i
Thus PL/SQL at Oracle9i delivers improved performance and functionality for the application and improved usability
for the developer.
1.3. HOW DOES THE USER CHOOSE BETWEEN INTERPRETED AND NATIVE COMPILATION MODES?
The compiler mode is determined by the session parameter plsql_compiler_flags. The user may set it thus…
…to set the compilation mode for subsequently compiled PL/SQL library units. The mode is stored with the library
unit’s metadata, so that if it is implicitly recompiled as a consequence of dependency checking then the mode the user
intended will be used. It may be inspected thus…
2 Paper # 129
Fast Track to Oracle9i
http://otn.oracle.com//tech/pl_sql/htdocs/README_2188517.htm
3 Paper # 129
Fast Track to Oracle9i
case n
when 1 then Action1;
when 2 then Action2;
when 3 then Action3;
else ActionOther;
end case;
…and…
if
n = 1 then Action1;
elsif n = 2 then Action2;
elsif n = 3 then Action2;
else ActionOther;
end if;
…are semantically almost identical. But coding best practice gurus generally recommend the CASE formulation
because it more directly models the idea. By pulling out the decision expression n to the start and by mentioning it
only once the programmer’s intention is clearer. This is significant both to the proof reader and to the compiler, which
therefore has better information from while to generate efficient code. For example, the compiler knows immediately
that the decision expression needs to be evaluated just once. And, since the IF formulation repeats the decision
expression for each leg, there’s a greater risk of typographical error which can be difficult to spot.
Moreover, the CASE formulation makes it explicit that the coded cases are the only ones that need handling (see the
discussion of the case_not_found exception below).
CASE constructs are available in most programming languages. Oracle9i introduces them in PL/SQL (and in SQL).
text := case n
when 1 then one
when 2 then two
when 3 then three
else other
end;
…and
if
n = 1 then text := one;
elsif n = 2 then text := two;
elsif n = 3 then text := three;
else text := other;
end if;
4 Paper # 129
Fast Track to Oracle9i
The CASE formulation makes it explicit that the intention of the fragment is to provide a value for text.
case
when n = 1 then Action1;
when n = 2 then Action2;
when n = 3 then Action3;
when ( n > 3 and n < 8 ) then Action4through7;
else ActionOther;
end case;
…and…
text := case
when n = 1 then one
when n = 2 then two
when n = 3 then three
when ( n > 3 and n < 8 ) then four_through_seven
else other
end;
Note: With the CASE formulation as with the IF formulation, the leg which is selected for particular data values will in
general depend on the order in which the legs are written. Consider…
case
when this_patient.pregnant = 'Y' then Action1;
when this_patient.unconscious = 'Y' then Action2;
when this_patient.age < 5 then Action3;
when this_patient.gender = 'F' then Action4;
else ActionOther;
end case;
...
p:=0; q:=0; r:=0;
case
when p = 1 then Action1;
when r = 2 then Action2;
when q > 1 then Action3;
end case;
exception
when case_not_found
5 Paper # 129
Fast Track to Oracle9i
for j in 1..n
loop
Show ( error_indexes(j), error_codes(j) );
end loop;
end;
Pre-Oracle9i there was no way to continue after a row-wise exception in the bulk binding approach…
forall j in words.first..words.last
insert into t ( text ) values ( words(j) );
…and the effect of the ORA-01401 on [what would be] just some of the rows meant that no rows are inserted.
Oracle9i introduces the save exceptions syntax and the corresponding “ORA-24381: error(s) in array DML”
exception. This allows the implied loop to continue after row-wise failure…
forall j in words.first..words.last
save exceptions /* new at 9i */
insert into t ( text ) values ( words(j) );
6 Paper # 129
Fast Track to Oracle9i
declare
...
bulk_errors exception;
pragma exception_init ( bulk_errors, -24381 );
begin
forall j in words.first..words.last
save exceptions
insert into t ( text ) values ( words(j) );
exception when bulk_errors then
for j in 1..sql%bulk_exceptions.count
loop
Show (
sql%bulk_exceptions(j).error_index,
Sqlerrm(-sql%bulk_exceptions(j).error_code) );
end loop;
end;
…which produces…
forall j in words.first..words.last
save exceptions
execute immediate 'insert into t ( text ) values ( :the_word )'
using words(j);
declare
type employee_ids_t is table of employees.employee_id%type
index by binary_integer;
employee_ids employee_ids_t; n integer:=0;
begin
for j in ( select employee_id from employees where salary < 3000 )
loop
n := n+1; employee_ids(n) := j.employee_id;
end loop;
end;
Each explicit row by row assignment of the collection element to the cursor component causes a context switch
between the PL/SQL engine and the SQL engine resulting in performance overhead. The following formulation (one
of a family of constructs generically referred to as bulk binding and available pre-Oracle9i)…
begin
select employee_id
bulk collect into employee_ids
from employees where salary < 3000;
7 Paper # 129
Fast Track to Oracle9i
end;
…substantially improves performance by minimizing the number of context switches required to execute the block.
(The above fragments work pre-Oracle 9i.)
There are many application implementation situations that require dynamic SQL. Native dynamic SQL (execute
immediate and related constructs) is usually preferred over Dbms_Sql because it’s easier to write and proof read
and executes faster. However, pre-Oracle9i, only Dbms_Sql could be used for dynamic bulk binding. Oracle9i
introduces the following syntax for bulk binding in native dynamic SQL …
begin /* new at 9i */
execute immediate 'select employee_id from employees where salary < 3000'
bulk collect into employee_ids;
end;
3.2.2. IN-BINDING
The same progression (explicit row by row processing, bulk binding, bulk binding in native dynamic SQL) is
supported for DML (insert, update and delete) thus…
for j in employee_ids.first..employee_ids.last
loop
update employees set salary = salary*1.1
where employee_id = employee_ids(j);
end loop;
…then…
forall j in employee_ids.first..employee_ids.last
update employees set salary = salary*1.1
where employee_id = employee_ids(j);
…then…
forall j in employee_ids.first..employee_ids.last
execute immediate 'update employees set salary = salary*1.1'
|| ' where employee_id = :the_id'
using employee_ids(j) /* new at 9i */;
3.2.3. OUT-BINDING
The progression is also supported for implicit query in a DML statement via the returning keyword…
for j in employee_ids.first..employee_ids.last
loop
update employees set salary = salary*1.1
where employee_id = employee_ids(j)
returning salary into salaries(j);
end loop;
…then…
forall j in employee_ids.first..employee_ids.last
update employees set salary = salary*1.1
where employee_id = employee_ids(j)
returning salary bulk collect into salaries
/* this is not a typo: employee_ids is subscipted but salaries isn’t */;
8 Paper # 129
Fast Track to Oracle9i
…then…
forall j in employee_ids.first..employee_ids.last
execute immediate 'update employees set salary = salary*1.1'
|| ' where employee_id = :the_id'
|| ' returning salary into :the_salary'
using employee_ids(j)
returning bulk collect into salaries /* new at 9i */;
9 Paper # 129
Fast Track to Oracle9i
It can be invoked with a cursor variable which has been assigned to any SELECT statement against any table whose
select list is a single VARCHAR2, for example…
declare
the_cursor sys_refcursor;
begin
open the_cursor for
select last_name from employees order by last_name;
Fetch_From_Cursor ( the_cursor );
close the_cursor;
Note: the available type sys_refcursor, defining a generic weak cursor, is a usability enhancement, new at Oracle9i.
Pre-Oracle9i it would be necessary to define at type, for example…
for j in the_names.first..the_names.last
loop
Show ( the_names(j) );
end loop;
end Bulk_Fetch_From_Cursor;
It can be invoked with a cursor variable which has been assigned using native dynamic SQL, thus…
declare
the_cursor sys_refcursor;
10 Paper # 129
Fast Track to Oracle9i
begin
open the_cursor for
'select last_name from employees order by last_name';
Bulk_Fetch_From_Cursor ( the_cursor );
close the_cursor;
If this is attempted in a pre-Oracle9i environment (making appropriate substitution for sys_refcursor), then: either
bulk fetch can be used when the cursor variable is assigned using static SQL; or explicit row by row fetch can be used
when the cursor variable is assigned using native dynamic SQL. But the attempt to do bulk fetch when the cursor
variable is assigned using native dynamic SQL causes “ORA-01001: invalid cursor”.
begin
for department in (
select department_id, department_name
from departments
order by department_name
)
loop
Show ( department.department_name );
for employee in (
select last_name
from employees
where department_id = department.department_id
order by last_name
)
loop
Show ( employee.last_name );
end loop;
end loop;
end;
The following SELECT expresses the query requirement in a single SQL statement …
select
department_name,
cursor (
select last_name
from employees e
where e.department_id = d.department_id
order by last_name
) the_employees
from departments d
order by department_name;
…and runs at SQL*Plus pre-Oracle9i. (This implies of course that a corresponding cursor can be manipulated in the
programming language used to implement SQL*Plus.) However, an attempt to associate such a SELECT statement
11 Paper # 129
Fast Track to Oracle9i
with a PL/SQL cursor pre-Oracle9i fails to compile (with PLS-00103). Oracle9i introduces support for this thus…
declare
cursor the_departments is
select
department_name,
cursor (
select last_name
from employees e
where e.department_id = d.department_id
order by last_name
)
from departments d
where department_name in ( 'Executive', 'Marketing' )
order by department_name;
v_department_name departments.department_name%type;
the_employees sys_refcursor;
Though this is more lines of code, and arguably less easy to proof read, than the sequentially programmed
implementation it has this advantage: there is only one SQL statement, and so it can be optimized more effectively
than (what the SQL engine sees as) two unconnected SQL statements.
Note: Bulk fetch is used for the_employees cursor. This is not currently available for the_departments cursor
because the appropriate collection type cannot be declared…
declare
type department_r is record
( department_name departments.department_name%type,
the_employees sys_refcursor );
begin null; end;
…causes “PLS-00989: Cursor Variable in record, object, or collection is not supported by this release”.
12 Paper # 129
Fast Track to Oracle9i
would be possible to invoke a PL/SQL procedure or function which has a formal parameter of type ref cursor
with a cursor expression as its actual parameter, thus…
In fact, this was not allowed under any circumstances pre-Oracle9i (ORA-22902 ). New at Oracle9i it is now allowed
under certain circumstances: when the function (it cannot be a procedure) is invoked in a top level SQL statement.
Given a function that can be invoked thus…
declare
the_cursor sys_refcursor;
n number;
begin
open the_cursor for
select my_col from my_tab;
n := My_Function ( the_cursor );
close the_cursor;
end;
…or…
Most significantly, this syntax is now allowed in the invocation of a table function in the FROM list of a SELECT
statement, see below.
Note: the following syntax…
begin
My_Function ( cursor ( select my_col from my_tab ) );
end;
…is not allowed. (It fails with “PLS-00405: subquery not allowed in this context”.)
13 Paper # 129
Fast Track to Oracle9i
But the approach in A.4.1.2, thought it works, feels back to front! Unlike A.4.1.1, it does not model the simple
statement of the algorithm, and is therefore hard to write and to proof read. A more comfortable approach is to define
a view thus…
…or by passing a cursor expression as the actual parameter to a function whose formal parameter is of type ref
cursor (see A.4.1.4) thus…
The A.4.1.4 approach is not possible before Oracle9i. Its advantage over the A.4.1.3 approach is marginal rather than
dramatic: it offers greater potential for reuse in that its logic is expressed in terms of, and depends only on, the select
list for an arbitrary SELECT whereas the A.4.1.3 approach hard-codes the SELECT; and, since there is only one SQL
statement, this can be optimized more effectively than (what the SQL engine sees as) two unconnected SQL
statements (as discussed above).
The dramatic benefit of the new Oracle9i feature allowing a cursor expression as an actual parameter to a PL/SQL
function come is connection with table functions, discussed below.
We can then write a PL/SQL function which returns an instance of the table thus…
14 Paper # 129
Fast Track to Oracle9i
This allows a table to be synthesized by computation. For example, the function might call Utl_File procedures (to
parse data that cannot be handled by the SQL*Loader utility or by the external table feature), or might call C routines
(via the callout framework) which access arbitrary external data sources. Or it might access database tables and
perform transformations which cannot be expressed with pure SQL and SQL functions. The SELECT statement can
be used to define a view, and/or combined with other tables in the FROM list in an arbitrarily complex SQL
statement.
A table function, then, is a PL/SQL function which can be invoked in the FROM clause of a SQL SELECT clause.
We’ll see below that a table function which exploits new Oracle9i functionality, which we expect all table functions to
do, can only be invoked in the FROM clause of a SQL SELECT clause.
15 Paper # 129
Fast Track to Oracle9i
Thus each row is delivered as soon as it is ready, so that the response time characteristics of a table function are
symmetrical with those of a rowsource based on a table scan or an index scan. (For performance, the PL/SQL
runtime system delivers the rows from a pipelined table function in batches.)
Note: the procedure body now mentions only rows (i.e. not the table), and the table is just implied by the return type.
(For elegance, the IF construct has been replaced with the new CASE formulation.) The same syntax as above can be
used to select from the table function, but it can now be simplified thus…
(The invocation will be written Lookups_Fn() in the following to emphasize its status as a function.)
Oracle9i also introduces the possibility to create a table function which returns a PL/SQL type thus…
In the limit, a PL/SQL type may be defined in the declare section of an anonymous block and hence have no
persistence. However, to be useful in connection with table functions, the PL/SQL types must be declared in a
package, and so when discussing table functions they are usually referred to as package-level types (in contrast to
schema-level types).
Note: A table function which returns a package-level type must be pipelined. Moreover, the simpler SELECT syntax
(without the CAST) must be used.
4.9. PIPING DATA FROM ONE TABLE FUNCTION TO THE NEXT – NEW IN ORACLE9i
A table function may now be defined with an input parameter of type ref cursor and invoked with a cursor
expression as the actual parameter. Consider the following…
16 Paper # 129
Fast Track to Oracle9i
case v_in_row.idx
when 1 then v_out_row.idx := 1*2; v_out_row.text := 'was one';
when 2 then v_out_row.idx := 2*3; v_out_row.text := 'was TWO';
when 3 then v_out_row.idx := 3*4; v_out_row.text := 'was three';
when 4 then v_out_row.idx := 4*5; v_out_row.text := 'was FOUR';
when 5 then v_out_row.idx := 5*6; v_out_row.text := 'was five';
when 6 then v_out_row.idx := 6*7; v_out_row.text := 'was SIX';
when 7 then v_out_row.idx := 7*8; v_out_row.text := 'was seven';
else v_out_row.idx :=
v_in_row.idx*10; v_out_row.text := 'was other';
end case;
pipe row ( v_out_row );
end loop;
close p_input_rows;
return;
end Mappings_Fn;
Suppose t is a table which supports a select list compatible with My_Types.lookup_row. We can now invoke the
table function thus…
Data can be piped from one to the next of an arbitrary number of table functions daisy-chained in succession. And
due to the pipelining feature storage of intermediate results is avoided. Table functions can thus be used to implement
the extraction, load and transformation operation (a.k.a. ETL) for building a datawarehouse from OLTP data. In the
limit, the extraction table function would access a foreign data source as discussed above.
4.10. THE “YOUNG MANAGERS” SCENARIO REVISITED – TABLE FUNCTION APPROACH
We can now use yet another approach! The complete solution can be implemented in a table function. This has the
usability advantage of keeping all the logic in one place, and the performance advantage of invoking the function only
once rather than once per row in the table. See code sample A.4.1.5 in the Appendix. This was derived “mechanically”
from code sample A.4.1.1 simply by creating an appropriate PL/SQL table type and by creating the block as a
pipelined function to return that type, substituting pipe row ( manager_employee_id ) for insert into
young_managers values ( manager_employee_id ).
The function can be made more general by giving it a ref cursor input parameter and by passing in the cursor
expression as the actual parameter. See code sample A.4.1.6. This would allow it to be “pointed at” any table which
17 Paper # 129
Fast Track to Oracle9i
18 Paper # 129
Fast Track to Oracle9i
4.12.1. SPECIAL CASE: FUNCTION BEHAVIOR IS INDEPENDENT OF THE PARTITIONING OF THE INPUT DATA
Consider a function which processes each row from its input cursor individually. (Such a transformation which
generates two or more output rows from each input row – generically referred to as piviotting - benefits particularly
from a table function implementation.) The syntax to parallelize this is straightforward thus…
See code sample A.4.2.1 in the Appendix for the complete working example. They keyword any expresses the
programmer’s assertion that the results are independent of the order in which the function gets the input rows. When
this keyword is used, the runtime system randomly partitions the data among the query slaves. This keyword is
appropriate for use with functions that take in one row, manipulate its columns, and generate output row(s) based on
the columns of this row only. (Of course if this assertion doesn’t hold, then the output will not be predictable.) This is
the small exception referred to above: the input ref cursor need not be strongly typed to be partitioned by any.)
The ability to exploit the parallel potential of a table function depends on whether the source can be parallelized.
4.12.2. GENERAL CASE: FUNCTION BEHAVIOR DEPENDS ON THE PARTITIONING OF THE INPUT DATA
Consider a transformation along the lines of…
…where the aggregation operation to be performed on the set of salaries for a given department is arbitrarily complex
such that a classical SQL implementation is impossible, slow by virtue of a function invocation for each row of the
source table, or prohibitively challenging to write and debug. For example, it might be that the cost to the employer of
paying a given salary depends on the hire date because of changes in benefits packages that affect only employees
hired after the date of change. This is illustrated in code sample A.4.2.2 in the Appendix, but to avoid obscuring it
with a complicated algorithm, the aggregation is simply the sum for the salary for each distinct department. This has
the general form…
19 Paper # 129
Fast Track to Oracle9i
Given that the input rows will be partitioned between different slaves, the integrity of the algorithm requires that all the
rows for a given department go to the same slave, and that all these rows are delivered consecutively. (Strictly speaking, the
requirement for consecutive delivery is negotiable, but the design of the algorithm to handle this case would need to
be much more elaborate. For that reason, Oracle commits to consecutive delivery.) We use the term clustered to signify
this type of delivery, and cluster key for the column (in this case “department”) on which the aggregations done. But
significantly, the algorithm does not care in what order of cluster key it receives each successive cluster, and Oracle does
not guarantee any particular order here.
This allows the possibility of a quicker algorithm than if rows were required to be clustered and delivered in order of
the cluster key. It scales as order N rather than order N.log(N), where N is the number of rows. The syntax is…
We can choose between hash (dept) and range (dept) depending on what we know about the distribution of
the values. (hash will be quicker than range and is the natural choice to be used with cluster... by.) Here, to be
partitioned by a specified column, the input ref cursor must be strongly typed. cluster... by is not allowed
without parallel_enable ( partition... by.
Note: at version 9.0.1 it is necessary to include ORDER BY on the cluster key in the SELECT used to invoke the table
function thus…
…to preserve correctness of behavior, but this restriction will be removed when the order N clustering algorithm is
productized.
20 Paper # 129
Fast Track to Oracle9i
return My_Types.items_tab
pipelined
order p_input_rows by (c1)
parallel_enable
( partition p_input_rows by range (c1) )
is...
This means that those rows that are delivered to a particular slave as directed by partition... by will be locally
sorted by that slave, thus parallelizing the sort. Therefore there should be no ORDER BY in the SELECT used to
invoke the table function. (To have one would subvert the attempt to parallelize the sort.) Thus it’s natural to use the
range option together with the order by option. This will be slower than cluster by, and so should be used only
when the algorithm depends on it.
Note: the cluster... by construct cannot be used together with the order... by in the declaration of a table
function. This means that an algorithm which depends on clustering on one key, c1, and then on ordering within the
set row for a given value of c1 by, say, c2 would have to be parallelized by using the order... by in the declaration
in the table function. (The algorithm in code sample A.5.5 has this character.) Here we would use…
The current restriction preventing using cluster... by together with order... by implies no loss of
functionality, but only a missed opportunity to leverage the order N sort.
Caution: It is possible to design an algorithm for a table function which would deliver a different number of rows
according to the degree of parallelism. The simplest example is a function which returns a table of NUMBER
representing the count of the rows its input cursor delivered. A non-parallelized version would deliver just one row
giving count(*) for the input table. A parallelized version would deliver N rows (where N is the degree of parallelism),
the sum of whose values would give count(*) for the input table. However, this breaks the parallel query abstraction.
Oracle recommends against programming this way.
21 Paper # 129
Fast Track to Oracle9i
5. MULTILEVEL COLLECTIONS
5.1. COLLECTIONS - RECAP
There are two schema-level collection prototypes: VARRAY and (nested) TABLE. Both define one-dimensional ordered
arrays of elements of a specified type, and can be leveraged in the creation of user-defined schema-level types thus…
…or…
Instances of schema-level types based on VARRAY or TABLE can be stored as fields of a column in a relational
database table thus…
22 Paper # 129
Fast Track to Oracle9i
The main difference between VARRAY and TABLE is that the former has a defined upper bound whereas the latter
is unbounded. A VARRAY field is stored inline for small sizes (<= 4000 bytes) and in an opaque system-managed
LOB within the given relational table for larger sizes, while a TABLE field is stored as several rows in a separate
opaque system managed relational table. This impacts the efficiency of access, leading to a generically familiar trade-
off: non-negotiable maximum collection size with faster access versus unlimited collection size with slower access.
PL/SQL allows variables of user-defined types and provides mechanisms for passing data stored in schema-level
collections to and from the corresponding PL/SQL structures thus…
declare
cursor c is select id, arr from t;
v_id number;
v_arr Arr_t;
begin
open c;
loop
fetch c into v_id, v_arr; exit when c%notfound;
Show ( v_id );
for j in v_arr.first..v_arr.last
loop
Show ( v_arr(j).a, v_arr(j).b, v_arr(j).c );
end loop;
end loop;
close c;
end;
PL/SQL also allows types based on VARRAY or TABLE to be declared within library units. This will typically be in a
package for reuse across several library units. In addition, PL/SQL allows the index-by variant of TABLE. (This variant
is not allowed as the basis of a schema-level type.)
All the above is supported pre-Oracle9i.
…but pre-Oracle9i the create type T2_tab_t statement fails with “PLS-00534: A Table type may not contain a
nested table type or VARRAY”.
Oracle9i adds support for this, allowing collection hierarchies of arbitrary depth. The corresponding syntax for types
defined within a PL/SQL library unit is also supported…
23 Paper # 129
Fast Track to Oracle9i
This fails pre-Oracle 9i with “PLS-00507: a PLSQL Table may not contain a table or a record with composite fields”.
The reln_training_logs approach would be suitable if the typical access was for ad hoc queries across runners,
and the nested_training_logs approach would be suitable if the typical access was to report all the information
for each of a number of selected runners.
We’ll look at code to populate and to report on the nested_training_logs table. And then we’ll see how table
functions can be written to “view” nested_training_logs as reln_training_logs and to “view”
reln_training_logs as nested_training_logs. By writing each with a ref cursor input parameter we can
conveniently test that the result of two successive transformations is identical to the starting data. See section A.5 in
the Appendix for the complete working code.
24 Paper # 129
Fast Track to Oracle9i
for v_row in
( select first_name, training_log from nested_training_logs )
loop
Show ( v_row.first_name );
for week in v_row.training_log.first..v_row.training_log.last
loop
Show ( week );
for run in v_row.training_log(week).first..v_row.training_log(week).last
loop
Show ( run );
Show ( v_row.training_log(week)(run).distance );
Show ( v_row.training_log(week)(run).pace );
end loop;
end loop;
end loop;
…where we see that appending each successive subscript to the variable representing the multilevel collection instance
drills down each successive layer in its structure.
If appropriate, this could be re-written using bulk collect into local multilevel collection (with one extra level) thus…
declare
type first_name_tab_t is table of nested_training_logs.first_name%type
index by binary_integer;
v_first_name_tab first_name_tab_t;
for j in v_first_name_tab.first..v_first_name_tab.last
loop
Show ( v_first_name_tab(j) );
for week in v_training_logs_tab(j).first..
v_training_logs_tab(j).last
loop
Show ( week );
for run in v_training_logs_tab(j)(week).first..
v_training_logs_tab(j)(week).last
loop
Show ( run );
Show ( v_training_logs_tab(j)(week)(run).distance );
Show ( v_training_logs_tab(j)(week)(run).pace );
end loop;
end loop;
end loop;
end;
25 Paper # 129
Fast Track to Oracle9i
26 Paper # 129
Fast Track to Oracle9i
…and then populate a second table for the relational representation, r2 by running N_to_R_Tab_Fn on n2,
leveraging the fact that N_to_R_Tab_Fn has a ref cursor input parameter. Then we do…
27 Paper # 129
Fast Track to Oracle9i
features for encoding and decoding XML, and for sending email from the database, but these are beyond the scope of
this paper.
Depending on the design of the workflow, state may need to be represented. For example, a customer might request a
price and delivery date for a given quantity of items from several vendors. Each vendor would reply with price and
delivery date and with an “offer good to” date. When the customer site sends a request to the selected vendor to place
a definite order, it will need to refer to the specific offer. If such a scheme is used within a single organization, for
example to communicate between databases at local offices in different countries, then the communication protocol
can be designed from scratch, and most likely an offer reference number will be exchanged as part of the XML
encoding. However, if the partners in the B2B relationship are completely independent, and especially if the
relationship is casual, then the requestor will have to follow whatever protocol the receiver has defined. It might be
that the receiver has implemented the state which represents an ongoing dialogue using cookies. In this case the
sender will need to handle these programmatically.
http://otn.oracle.com/tech/pl_sql/
28 Paper # 129
Fast Track to Oracle9i
their bytes using the %nn notation. (The sender of the request must know from documentation what character set the
URL expects to decode from the hex representation.) Oracle9i introduces the Utl_Url package which has functions
to convert from the database character set to a hex coded representation of a specified character set, and vice versa. In
addition, these functions handle the conversion of the reserved symbols: percent (%), semi-colon (;) slash (/),
question mark (?), colon (:), at sign (@), ampersand (&), equals sign (=), plus sign (+), dollar sign ($), and comma (,).
When sending by the “PUT” method, the character set of the request body should be set via the charset attribute of
the Content-Type in the request header, using the new Utl_Http.Set_Header procedure. If this is done, it gives
Oracle sufficient information to transform appropriately when sending a character request body (by using
Utl_Http.Write_Text). If the charset attribute is not set in the request header, then no character set
conversion takes place unless the user has catered for it via the overloaded procedure
Utl_Http.Set_Body_Charset. The variant Set_Body_Charset(charset varchar2) – a.k.a. the global
variant - allows the user to set a fallback character set, to be assumed, if no other information is provided, for both
requests and responses for the session. The variant Set_Body_Charset(r Utl_Http.Req, charset
varchar2) – a.k.a the request variant, allows the user to insist on a character set for the body for this request. (A
record of PL/SQL type Utl_Http.Req is returned when the HTTP request is begun with
Utl_Http.Begin_Request.) The choice made via the request variant will not only override that made via the
global variant but will also override that made via the charset attribute of the request header. For this reason, the
recommended way to specify the character set conversion for the request body is via the charset attribute of the
header. Only if the user has a special reason for leaving this unspecified in the request header would he use the request
variant of Set_Body_Charset.
There is just one area of concern when receiving the response: the response body. If the implementation of the URL
is well-mannered, then the character set of the response body will be specified correctly in the charset attribute of
the Content-Type in the response header, accessible to the user via the procedure Utl_Http.Get_Header. Oracle
will implicitly perform the appropriate conversion in connection with calling Utl_Http.Read_Text. However, this
is often not set. In this case the user can use the global variant of Set_Body_Charset to determine the character set
conversion. However, the charset attribute of the response header is sometimes set wrong. (This is likely when pages
in different character sets are served up as files from the filesystem seen by the webserver, since the Content-Type
header information will often be set globally for the server with no mechanism to make it file specific.) For this reason
a third overloaded variant Set_Body_Charset(r Utl_Http.Resp,charset varchar2) is provided – a.k.a.
the response variant. (A record of PL/SQL type Utl_Http.Resp is returned when the HTTP response is got with
Utl_Http.Get_Response.) The choice made via the response variant will override that made via the global variant
and that expressed via the charset attribute of the response header.
Note: from Oracle8i v8.1.6 and pre-Oracle9i, Oracle detected the charset of the response body (if this was specified)
and used the information to do the character set conversion. And if the charset attribute of the response body was
not specified then no conversion took place and no overriding or fallback mechanism was provided. Under special
circumstances (eg fetching a SJIS Japanese response where the charset attribute is not specified into a EUC
database) problems arose pre-Orcale9i.
Thus the user now has full control over all character set conversion issues. In an extreme case, where the response
body is Content-Type text/html and where the HTML <meta> tag is used to specify the character set, the user can
retrieve the response body into a PL/SQL RAW with Utl_Http.Read_Raw and then write custom code to parse
the HTML and to convert to the database character set in a PL/SQL VARCHAR2 once the response character set is
discovered.
7. TRANSPARENT ENHANCEMENTS
Record construction and copying is now faster in Oracle9i. Benchmarks designed to stress this feature improved by up
to 5 times. Less focused benchmarks improved by 5 to 10%.
29 Paper # 129
Fast Track to Oracle9i
There is considerable improvement in the execution of PL/SQL programs that reference subprograms that are part of
another package. Benchmarks specifically designed to test this feature showed a speedup of over 50%. In more generic
benchmarks an improvement of 5% has been seen.
There is a 60%, or more, reduction in overhead of calling PL/SQL procedures from SQL statements. Thus the
execution of SQL statements that reference subprograms is faster.
The SQL parser replaces PL/SQL’s compile-time analysis of a static SQL statement with analysis using a SQL
component shared with the RDBMS. Therefore duplication of SQL analysis is reduced. PL/SQL is allowed to pick up
new SQL features as they are implemented in the RDBMS. Errors due to differences in SQL analysis between SQL
and PL/SQL are also eliminated.
The Utl_Tcp package has been reimplemented (moving from java to native C) to deleiver increased performance.
Though not strictly speaking transparent changes, we list here for convenience restrictions that have been removed:
it’s now possible to assign (for example) a variable of type VARCHAR2 to one of type NVARCHAR2 and enjoy
implicit conversion; it’s now possible to assign aVARCHAR2 to a CLOB and (provided the CLOB isn’t too big) vice
versa, and to use substr and instr with CLOB variables.
BUSINESS BENEFITS
• Increased speed and scalability
• Improved usability for the developer
BUSINESS BENEFITS
• Seamless access to SQL features
declare
person_var person_type; /* can denote an object of this type
or of any of its subtypes */
begin
person_var := person_type(...);
person_var.some_method(); /* invokes some_method() of person */
30 Paper # 129
Fast Track to Oracle9i
BUSINESS BENEFITS
• functional and operational object oriented completeness
Note: The full treatment of Oracle’s object oriented functionality is beyond the scope of this paper.
10. NEW OR ENHANCED SUPPLIED PACKAGES
New packages: Dbms_Xmlgen (creates an XML document from any SQL query, returning the result as a CLOB);
Dbms_Metadata (provides interfaces for extracting complete definitions of database objects either as XML or as
SQL DDL); dbms_aqelm, dbms_encode, dbms_fga, dbms_flashback, dbms_ldap,
dbms_libcache, dbms_logmnr_cdc_publish, dbms_logmnr_cdc_subscribe, dbms_odci,
dbms_outln_edit, dbms_redefinition, dbms_transform, dbms_url, dbms_wm,
dbms_xmlquery, dmbs_xmlsave, utl_encode.
New Types: XMLtype, UriType, DBUriType, and HttpUriType, dbms_types, anydata_type,
anydataset_type, anytype_type.
Enhanced packages: Utl_Raw (enhanced with these new APIs: Cast_To_Number, Cast_From_Number,
Cast_To_Binary_Integer, Cast_From_Binary_Integer); Utl_File; Utl_Http (as discused at length
above).
BUSINESS BENEFITS
• Increased out-of-the-box functionality
begin
for department in ( select department_id d, department_name from departments
order by department_name )
loop
Dbms_Output.Put_Line ( Chr(10) || department.department_name );
for employee in ( select last_name from employees
where department_id = department.d
order by last_name )
31 Paper # 129
Fast Track to Oracle9i
loop
Dbms_Output.Put_Line ( '- ' || employee.last_name );
end loop;
end loop;
end;
This example when run against prepared data to give 12 million iterations, i.e. approximately the same number as A.1.2
below runs about 3% faster when compiled in native mode. Note: the example could be re-written to run more
efficiently by using a single SELECT with a CURSOR subquery as described in the section on cursor expressions.
There are many examples of this formulation throughout this paper.
32 Paper # 129
Fast Track to Oracle9i
begin /* Perfect_Triangles */
t1 := Dbms_Utility.Get_Time;
for long in 1..p_max
loop
for short in 1..long
loop
hyp := Sqrt ( long*long + short*short ); ihyp := Floor(hyp);
if hyp-ihyp < 0.01
then
if ( ihyp*ihyp = long*long + short*short )
then
if Sides_Are_Unique ( long, short )
then
m := m+1;
unique_sides(m).long := long; unique_sides(m).short := short;
Store_Dup_Sides ( long, short );
end if;
end if;
end if;
end loop;
end loop;
t2 := Dbms_Utility.Get_Time;
Dbms_Output.Put_Line (
chr(10) || To_Char( ((t2-t1)/100), '9999.9' ) || ' sec' );
end Perfect_Triangles;
manager_employee_id employees.employee_id%type;
manager_hire_date employees.hire_date%type;
reports sys_refcursor;
type report_hire_dates_tis table of employees.hire_date%type
index by binary_integer;
report_hire_dates report_hire_dates_t;
before integer; after integer;
begin
open managers;
loop
33 Paper # 129
Fast Track to Oracle9i
before:=0; after:=0;
fetch managers into manager_employee_id, manager_hire_date, reports;
exit when managers%notfound;
fetch reports bulk collect into report_hire_dates;
if report_hire_dates.count > 0
then
for j in report_hire_dates.first..report_hire_dates.last
loop
null;
case report_hire_dates(j) < manager_hire_date
when true then before:=before+1;
else after:=after+1;
end case;
end loop;
end if;
if before > after then
insert into young_managers values ( manager_employee_id ); end if;
end loop;
close managers;
end;
34 Paper # 129
Fast Track to Oracle9i
35 Paper # 129
Fast Track to Oracle9i
manager_employee_id employees.employee_id%type;
manager_hire_date employees.hire_date%type;
reports sys_refcursor;
type report_hire_date_tis table of employees.hire_date%type
index by binary_integer;
report_hire_dates report_hire_date_t;
before integer; after integer;
begin
open managers;
loop
before:=0; after:=0;
fetch managers into manager_employee_id, manager_hire_date, reports;
exit when managers%notfound;
fetch reports bulk collect into report_hire_dates;
if report_hire_dates.count > 0
then
for j in report_hire_dates.first..report_hire_dates.last
loop
case report_hire_dates(j) < manager_hire_date
when true then before:=before+1;
else after:=after+1;
end case;
end loop;
end if;
if before > after then
pipe row ( manager_employee_id ); end if;
end loop;
close managers;
return;
end Young_Managers_Fn;
36 Paper # 129
Fast Track to Oracle9i
Note: An attempt to create a view as the above select statement currently fails with “ORA-22902: CURSOR expression
not allowed” where the exception is raised because the SELECT statement which is the argument of the CURSOR
formal parameter to the table function itself has a cursor expression (a.k.a. cursor subquery). A view can be created
when SELECT statement does not have a cursor subquery (see the Mappings_Fn example above).
37 Paper # 129
Fast Track to Oracle9i
A.4.2.2. ALGORITHM REQUIRES ONLY THAT THE SOURCE ROWS ARE CLUSTERED
Note: in order to avoid having to make the algorithm distractingly complex, this DELETE should be issued...
38 Paper # 129
Fast Track to Oracle9i
g_got_a_row boolean;
g_new_dept boolean;
g_current_dept employees.department_id%type;
g_prev_dept employees.department_id%type;
v_total_sal number;
begin
Get_Next_Row();
while Got_Next_Dept()
loop
v_total_sal := 0;
while Got_Next_Row_In_Dept()
loop
v_total_sal := v_total_sal + g_in_row.sal;
Get_Next_Row();
end loop;
g_out_row.sal := v_total_sal; g_out_row.dept := g_current_dept;
pipe row ( g_out_row );
end loop;
close p_input_rows;
return;
end Aggregate_Xform;
39 Paper # 129
Fast Track to Oracle9i
Note the syntax of the query. Since the table function returns an object, it follows from the syntax against an object
table above. Again, it’s convenient to encapsulate it in a view.
40 Paper # 129
Fast Track to Oracle9i
Note the syntax of the query. It’s most compactly expressed using the views v or lookups defined above.
select value(b)
from table
(
cast
(
Mappings_Fn
(
cursor ( select * from lookups )
)
as lookups_tab
)
) b;
41 Paper # 129
Fast Track to Oracle9i
Mappings_Fn
(
cursor
( select value(a) from table
(
cast ( Lookups_Fn() as lookups_tab )
) a
)
)
as lookups_tab
)
) b;
We can now access the from PL/SQL without restriction, for example…
declare
cursor table_fn_cur is
select * from mapped_lookups;
rec lookup_row;
begin
open table_fn_cur;
loop
fetch table_fn_cur into rec;
exit when table_fn_cur%notfound;
Print ( rec.idx, rec.text );
end loop;
close table_fn_cur;
end;
begin
for j in ( select * from mapped_lookups )
loop
Show ( j.rec.idx, j.rec.text );
end loop;
end;
42 Paper # 129
Fast Track to Oracle9i
43 Paper # 129
Fast Track to Oracle9i
v_training_log(1) :=
weeks_running_t
(
run_t ( 1, 6 ),
run_t ( 7, 7 ),
run_t ( 3, 6 ),
run_t ( 9, 9 ),
run_t ( 3, 6 ),
run_t ( 18, 10 )
);
v_training_log.extend;
v_training_log(2) :=
weeks_running_t
(
run_t ( 5, 7 ),
run_t ( 9, 8 ),
run_t ( 3, 7 ),
run_t ( 9, 9 ),
run_t ( 3, 7 )
);
v_training_log.extend;
v_training_log(3) :=
weeks_running_t
(
run_t ( 5, 7 ),
run_t ( 9, 8 ),
run_t ( 3, 7 ),
run_t ( 9, 9 ),
run_t ( 3, 7 )
) ;
v_training_log(1) :=
weeks_running_t
(
run_t ( 2, 10 ),
run_t ( 3, 11 ),
run_t ( 3, 11 ),
run_t ( 4, 12 )
);
v_training_log.extend;
v_training_log(2) :=
weeks_running_t
(
run_t ( 1, 10 ),
run_t ( 2, 11 ),
run_t ( 3, 12 ),
44 Paper # 129
Fast Track to Oracle9i
run_t ( 2, 10 ),
run_t ( 1, 9 ),
run_t ( 4, 12 )
);
45 Paper # 129
Fast Track to Oracle9i
loop
v_out_row.first_name := v_in_row.first_name;
v_out_row.week := week;
v_out_row.run := run;
v_out_row.distance := v_in_row.training_log(week)(run).distance;
v_out_row.pace := v_in_row.training_log(week)(run).pace;
pipe row ( v_out_row );
end loop;
end loop;
end loop;
close p_nested_training_logs;
return;
end Reln_Training_Logs_Fn;
46 Paper # 129
Fast Track to Oracle9i
end;
end case;
g_prev_first_name := g_in_row.first_name;
g_prev_week := g_in_row.week;
end if;
return;
end Get_Next_Row;
begin
Get_Next_Row();
47 Paper # 129
Fast Track to Oracle9i
while Got_Next_Runner()
loop
New_Training_Log;
while Got_Next_Week()
loop
New_Weeks_Running;
while Got_Next_Run()
loop
Store_This_Run;
Get_Next_Row();
end loop;
Store_This_Weeks_Running;
end loop;
OutPut_This_Runner; pipe row ( g_out_row );
end loop;
close p_reln_training_logs;
return;
end Nested_Training_Logs_Fn;
48 Paper # 129
Fast Track to Oracle9i
Reln_Training_Logs_Fn
(
cursor
(
select first_name, training_log from nested_training_logs_2
)
)
)
);
declare
req Utl_Http.Req;
resp Utl_Http.Resp;
name varchar2(255);
value varchar2(1023);
v_msg varchar2(80);
v_url varchar2(32767) := 'http://otn.oracle.com/';
begin
/* request that exceptions are raised for error Status Codes */
Utl_Http.Set_Response_Error_Check ( enable => true );
Utl_Http.Set_Proxy (
proxy => 'www-proxy.us.oracle.com',
no_proxy_domains => 'us.oracle.com' );
req := Utl_Http.Begin_Request (
url => v_url,
method => 'GET' );
/*
Alternatively use method => 'POST' and Utl_Http.Write_Text to
build an arbitrarily long message
*/
Utl_Http.Set_Authentication (
r => req,
username => 'SomeUser',
password => 'SomePassword',
scheme => 'Basic',
for_proxy => false /* this info is for the target web server */ );
Utl_Http.Set_Header (
r => req,
49 Paper # 129
Fast Track to Oracle9i
begin
loop
Utl_Http.Read_Text (
r => resp,
data => v_msg );
Dbms_Output.Put_Line ( v_msg );
end loop;
exception when Utl_Http.End_Of_Body then null;
end;
/* code for all the other defined exceptions you can recover from */
50 Paper # 129