Tải bản đầy đủ
PLVtab: Easy Access to PL/SQL Tables

PLVtab: Easy Access to PL/SQL Tables

Tải bản đầy đủ

[Appendix A] Appendix: PL/SQL Exercises
vc30_table
PL/SQL

table of VARCHAR2(30) strings

vc60_table
PL/SQL

table of VARCHAR2(60) strings

vc80_table
PL/SQL

table of VARCHAR2(80) strings

vc2000_table
PL/SQL

table of VARCHAR2(2000) strings

ident_table
PL/SQL

table of VARCHAR2(100) strings; matches PLV.plsql_identifier
declaration.

vcmax_table
PL/SQL

table of VARCHAR2(32767) strings
Let's compare the "native" and PL/Vision approaches to defining PL/SQL tables. In the following anonymous
block, I define a PL/SQL table of Booleans without the assistance of PLVtab.
DECLARE
TYPE bool_tabtype IS TABLE OF BOOLEAN
INDEX BY BINARY_INTEGER;
yesno_tab bool_tabtype;
BEGIN

With the PLVtab package in place, all I have to is the following:
DECLARE
yesno_tab PLVtab.boolean_table;
BEGIN

Once you have declared a table using PLVtab, you manipulate that table as you would a table based on your
own table TYPE statement.

7.4 Controlling Output
from p

8.2 Displaying PLVtab
Tables

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

8. PLVtab: Easy Access to PL/SQL Tables

277

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.2 Displaying PLVtab Tables
For each type of table, PLVtab provides a display procedure to show the contents of the table. As a result,
there are nine, overloaded versions of the display procedure. The headers for each of these programs are
the same, except for the datatype of the first parameter (the kind of table to be displayed).
Here, for example, is the specification of the procedure to display a date table:
PROCEDURE display
(tab_in IN date_table,
end_in IN INTEGER,
hdr_in IN VARCHAR2 := NULL,
start_in IN INTEGER := 1,
failure_threshold_in IN INTEGER := 0,
increment_in IN INTEGER := +1);

As you can see, there are lots of parameters, and that means lots of flexibility in specifying what rows are
displayed and the format of the display. Here is an explanation of the various arguments:
Argument

Description

tab_in

The PL/SQL table you want to display. The table type must be one of those
predefined in PLVtab.

end_in

The last row you want displayed. This is required. Until PL/SQL Release 2.3
there is no way for PLVtab to know the total number of rows defined in the
table. As you will see below, you can also specify the starting row, which
defaults to 1.

hdr_in

The header you want displayed before the individual rows are written out
using the p.l procedure.

start_in

The first row you want displayed. The default value is 1. This is placed after
the end_in argument because in almost every case it will not need to be
specified.

failure_threshold_in The number of times the display program can reference an undefined row in
the table before it stops trying any more. Remember: PL/SQL tables are
sparse. Consecutive rows do not need to be defined, but the display program
does need to move sequentially through the table to display its rows.
The increment used to move from the current row to the next row. The
default value is 1, but you could ask display to show every fifth row by
passing a value of 5.
The following examples illustrate how the different arguments are used.
increment_in

278

[Appendix A] Appendix: PL/SQL Exercises

8.2.1 Displaying Wrapped Text
The display_wrap program of the PLVprs package takes advantage of the PLVtab package in several
ways. It declares and uses a VARCHAR2(2000) table to receive the output from the wrap procedure, which
wraps a long string into multiple lines, each line of which is stored in a row in the PL/SQL table. This table is
then displayed with a call to the display procedure. Notice that display_wrap also turns off the PLVtab
header and sets the prefix before performing the display. These toggles for PLVtab are discussed in the next
section.
PROCEDURE display_wrap
(text_in IN VARCHAR2,
line_length IN INTEGER := 80,
prefix_in IN VARCHAR2 := NULL)
IS
lines PLVtab.vc2000_table;
line_count INTEGER := 0;
BEGIN
PLVtab.noshow_header;
PLVtab.set_prefix (prefix_in);
wrap (text_in, line_length, lines, line_count);
PLVtab.display (lines, line_count);
END;

Notice that in this call to display I employ most of the defaults: a NULL header, a starting row of 1, a
failure threshold of 0 (all rows should be defined), and an increment of 1. I do not want a header since I am
essentially using display as a utility within another program.

8.2.2 Displaying Selected Companies
Suppose that I have populated a PL/SQL table with company names, where the row number is the primary
key or company ID. I am, therefore, not filling the PL/SQL table sequentially. By keeping track of the lowest
and highest row used in the table, however, I can still display all the defined rows in the PL/SQL table as
shown below.
First, the package containing the data structures associated with the list of company names:
PACKAGE comp_names
IS
/* The table of names. */
list PLVtab.vc80_table;
/* The lowest row number used. */
lo_row BINARY_INTEGER := NULL;
/* The highest row number used. */
hi_row BINARY_INTEGER := NULL;
END comp_names;

Then various programs have been called to fill up the PL/SQL table with any number of company names. The
following call to display will show all defined rows regardless of how many there are, and how many
undefined rows lie between company names.
PLVtab.display
(comp_names.list,
comp_names.hi_row,
'Selected Company Names',
comp_names.lo_row,
comp_names.hi_row − comp_names.lo_row);

Let's look at a concrete example. Row 1506 is assigned the value of ACME, while row 20200 contains the
company name ArtForms. I can then make the above call to PLVtab.display and get the following results
8.2.1 Displaying Wrapped Text

279

[Appendix A] Appendix: PL/SQL Exercises
displayed on the screen:
Selected Company Names
ACME
ArtForms

You will probably be surprised to hear that it took more than 83 seconds on my Pentium 90Mhz laptop to
produce these results. Why so long a delay? The display procedure displayed row 1506 and then attempted
unsuccessfully 18,693 times to retrieve the rows between 1506 and 20200. Each time display referenced an
undefined row, the PL/SQL runtime engine raised the NO_DATA_FOUND exception, which was ignored.
The conclusion you should draw from this example is that PLVtab.display does a great job of hiding
these kinds of details, but it is still important for you to understand the architecture of PL/SQL tables. This
understanding will help you explain what would otherwise be an absurdly slow response time −− and also
help you decide when to take advantage of the PLVtab.display procedure. If your defined rows are
dispersed widely, PLVtab.display may not be efficient enough a method to display the contents of your
table.

8.1 Using PLVtab−Based
PL/SQL Table Types

8.3 Showing Header
Toggle

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

8.2.1 Displaying Wrapped Text

280

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.3 Showing Header Toggle
The PLVtab package is designed to be a generic, low−level utility for working with PL/SQL tables. As such,
it needs to be as flexible as possible when it comes to displaying the contents of these tables. I found that I
wanted to use PLVtab.display both to:
1.
Dump the contents of a table for debugging and verification purposes; and
2.
Display table contents from within other PL/Vision utilities and packages.
In the first use of PLVtab.display, I could rely on the default header for the table, which is simply:
Contents of Table

since I just wanted to see the results. When I am using PLVtab from within another environment or utility, I
need to be able to carefully control the format of the output. In some cases I will want to provide an
alternative header, which is done through the parameter list of the display procedure. In other situations, I may
want to avoid a header altogether.
The "show header" toggle offers this level of flexibility. The default/initial setting for PLVtab is to display a
header with the table. You can turn off the toggle by executing the "no show" procedure as follows:
SQL> exec PLVtab.noshowhdr

In this mode, even if you provide an explicit header in your call to display, that information will be ignored.
Only the row information will be displayed.

8.2 Displaying PLVtab
Tables

8.4 Showing Row Number
Toggle

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

281

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.4 Showing Row Number Toggle
The "show row" toggle allows you to specify whether or not you want the row numbers to be displayed along
with the contents of the row. The default value for this setting is no row numbers. Turn on the display of row
numbers as follows:
SQL> exec PLVtab.showrow

and then when you display the contents of a table, you will see Row N = prefixed to each row value as shown
in this output from PLVtab.display:
Row 1505 = ACME
Row 20200 = ArtForms

8.3 Showing Header
Toggle

8.5 Setting the Display
Prefix

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

282

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.5 Setting the Display Prefix
The PLVtab set_prefix procedure allows you to specify a prefix that is to be displayed before the row
values. This prefix is only displayed when you are not showing the row numbers. The default value for the
prefix is NULL, which means that you don't see any prefix unless you call the set_prefix program. The
header for this procedure is:
PROCEDURE set_prefix (prefix_in IN VARCHAR2 := NULL)

Since the single argument has a default value of NULL, you can set the prefix back to its default value simply
by entering this command:
SQL> PVLtab.set_prefix;

The following script shows you how the prefix is set and used in the display of PLVtab table information.
DECLARE
nms PLVtab.vc80_table;
lo INTEGER;
hi INTEGER;
BEGIN
PLVtab.noshowrow;
PLVtab.set_prefix ('Company Name = ');
nms (1505) := 'ACME';
nms (20200) := 'ArtForms';
lo := 1505;
hi := 20200;
PLVtab.display (nms, hi, 'Selected Company Names', lo, hi−lo);
END;
/
Selected Company Names
Company Name = ACME
Company Name = ArtForms

8.4 Showing Row Number
Toggle

8.6 Emptying Tables with
PLVtab

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

283

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.6 Emptying Tables with PLVtab
For PL/SQL Releases 2.2 and earlier, the only way to delete all rows from a PL/SQL table (and release all
associated memory) is to assign an empty table of the same TYPE to your structure. PLVtab offers the
following set of empty tables to facilitate this process for PLVtab−based tables:
empty_boolean boolean_table;
empty_date date_table;
empty_integer integer_table;
empty_number number_table;
empty_vc30 vc30_table;
empty_vc60 vc60_table;
empty_vc80 vc80_table;
empty_vc2000 vc2000_table;
empty_vcmax vcmax_table;
empty_ident ident_table;

It is very easy to use these empty tables (of course, they are only empty if you do not define rows in those
PL/SQL tables!). The following example shows a package body that has defined within it a PL/SQL table.
This table is then modified and emptied by the program units defined in that same package body.
PACKAGE BODY paid_subs
IS
listcount INTEGER := 0;
namelist PLVtab.vc80_table;
PROCEDURE addsub (name_in IN VARCHAR2) IS
BEGIN
namelist (listcount + 1) := name_in;
listcount := listcount + 1;
END;
PROCEDURE clearlist IS
BEGIN
namelist := PLVtab.empty_vc80;
END;
END paid_subs;

If you have PL/SQL Release 2.3, you don't have to bother with these empty tables. Instead, you can use the
PL/SQL table DELETE attribute to remove the rows from the table. The following examples illustrate the
power and flexibility of this syntax:
namelist.DELETE; −− Delete all rows.
namelist.DELETE (5); −− Delete row 5.
namelist.DELETE (5, 677); −− Delete all rows between 5 and 677.

This is obviously a much more desirable technique −− and it highlights a drawback to the PLVtab approach
to emptying tables.

284

[Appendix A] Appendix: PL/SQL Exercises

8.6.1 Improving the Delete Process
As explained above, to delete all the rows from a (PL/SQL Release 2.2 and earlier) PLVtab table, you would
assign an empty table to that table. The problem with this approach is that it exposes the implementation of
the delete process. You have to know about the empty table and also the aggregate assignment syntax. Worse,
when you do upgrade to PL/SQL Release 2.3 or above, you have to go to each of these assignments and
change the code in order to take advantage of the new attribute.
A much better approach would be for PLVtab to provide not the empty tables themselves, but procedures that
do the emptying for you. Such a program is very simple and is shown below:
PROCEDURE empty (table_out OUT date_table) IS
BEGIN
table_out := empty_date;
END;

This procedure would, of course, have to be overloaded for each table TYPE. Notice that this program uses
the empty table just as you would, but that detail is hidden from view. There are two advantages to this
approach:

Now when I want to empty a table, I simply call the program as shown below:
PLVtab.empty (my_table);

I don't have to know about the empty tables and their naming conventions. I leave that to the package.

When my installation upgrades to PL/SQL Release 2.3, I can take immediate advantage of the
DELETE operator without changing those parts of my application that empty my tables. Instead, I can
simply change the implementation of the empty procedure itself. I can implement a procedure with
equivalent functionality as follows:
PROCEDURE empty (table_out OUT date_table) IS
BEGIN
table_out.DELETE;
END;

Yet I could also enhance the empty procedures of PLVtab to take full advantage of the flexibility offered by
the DELETE attribute:
PROCEDURE empty
(table_out OUT date_table,
start_in IN INTEGER := NULL,
end_in IN INTEGER := NULL)
IS
BEGIN
table_out.DELETE
(NVL (start_in, table_out.FIRST),
NVL (end_in, table_out.LAST));
END;

Through careful assignment of default values for the arguments of this new implementation, all previous uses
of the empty procedure would still be valid. Future uses could take advantage of the new arguments.[1]
[1] Why isn't this technique used in PLVtab? Well, at some point, I had to stop changing my
code and instead write a book about it. You are, at least, now aware of the issue and can
implement this approach yourself.
8.6.1 Improving the Delete Process

285

[Appendix A] Appendix: PL/SQL Exercises
Special Notes on PLVtab
The PLVtab package supports only the table types listed in Table 8−1. You can add additional table types
easily, but be sure to make each of these changes:

Add the table TYPE statement for the desired datatype.

Declare an "empty" table of that same table type.

Create another version of the display procedure that accepts this table type.

8.5 Setting the Display
Prefix

8.7 Implementing
PLVtab.display

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

8.6.1 Improving the Delete Process

286

Chapter 8
PLVtab: Easy Access to
PL/SQL Tables

8.7 Implementing PLVtab.display
I faced several challenges when building the display procedures:

I had to create a separate procedure for each of the different table types, but I did not want to actually
have separate display engines for each table type; the code would be very cumbersome and lengthy.
Yet consolidating this code would also be difficult since each display procedure drew its information
from a different PL/SQL table.

With the implementation of PL/SQL tables prior to Release 2.3 of PL/SQL, it is impossible to obtain
information about the state of a PL/SQL table from the runtime engine. Instead, you must keep track
of the rows that have been used or be ready to handle the NO_DATA_FOUND exception.
I took care of the code redundancy problem by creating a single internal display procedure (idisplay) that
is called by each of the public display procedures. Here is an example of the full body of the display
procedure for date tables:
PROCEDURE display
(tab_in IN date_table,
end_in IN INTEGER,
hdr_in IN VARCHAR2 := NULL,
start_in IN INTEGER := 1,
failure_threshold_in IN INTEGER := 0,
increment_in IN INTEGER := +1)
IS
BEGIN
idate := tab_in;
idisplay
(c_date, end_in, hdr_in, start_in,
failure_threshold_in, increment_in);
idate := empty_date;
END;

What is going on here? First, I copy the incoming table into a private PL/SQL table (idate). Then I display
the contents of the idate table and not the user's table. Finally, I empty the internal PL/SQL table to
minimize memory utilization. Notice that the idate table does not appear in the parameter list for
idisplay. Instead, I pass in a constant, c_date, to indicate that idisplay should get the row values
from the idate table.
I have, then, achieved my first objective: Use a single procedure to implement all of the different overloaded
versions (this follows the diamond effect described in Chapter 4, Getting Started with PL/Vision). Of course,
all I have really done is move the complexity down into the idisplay procedure. At least, however, all the
complexity is concentrated into that single module.
But when you look inside the idisplay procedure you will find that there is actually very little complexity
there either. Instead, I further buried the "how" of displaying all these different types of PL/SQL tables in
287