Professional Documents
Culture Documents
I
N the past two issues of Oracle Professional, I’ve written a pair of articles on Windows 2000
about Oracle’s object types that illustrated how they can be intelligent data Stan Novinsky
structures—that is, data structures bound to procedures and functions. 14 LOG4PLSQL Oracle Database
As a software tool, that was pretty much the limit of their capabilities prior Logging Tools
to Oracle9i. With Oracle9i, however, object types now have an even more Guillaume Moulard
powerful feature: inheritance.
16 April 2004 Downloads
In this article I discuss the construction of a “class hierarchy”: a group of
object types for managing unformatted output. This project began when I first
started thinking about writing an object type wrapper for dbms_output. I
initially had two primary motivations. First, I use dbms_output for debugging
purposes, but I also send the same information to other destinations, Indicates accompanying files are available online at
www.pinnaclepublishing.com.
particularly CLOBs. I thought object types could be used to make the
destination of logging information transparent. Second, I not instantiable not final member procedure
write( pText in varchar2),
wanted to have more control over the state of output than member procedure writeLn( pText in varchar2),
just “on” and “off”—it’s common for me at entry to a not instantiable member procedure
writeRaw( pRaw in raw ),
procedure to want to enable or disable output, and to -- enable and disable push 1 and 0 respectively.
restore the previous state on exit. Using a stack to member procedure push( pInteger in number ),
member procedure enable,
maintain state is cleaner. member procedure disable,
The main components of dbms_output are PUT -- restore does a pop from the state stack,
-- preventing any underflows.
and PUT_LINE (I don’t use ENABLE or DISABLE). member procedure restore,
Obviously these are trivial to implement. However, add a -- isActive returns true if active
member function isActive return boolean,
constructor, flush, and close procedure and you have the -- some subtypes will do buffering, and may
basic structure of the OutputStream classes in Java. Add a -- require a method to flush the buffer
member procedure flush,
couple of methods for maintaining state and you have the member procedure initializeStateStack
framework of an object type that can be implemented for
) NOT INSTANTIABLE NOT FINAL
many different types of output destinations, and for
managing and transforming that output as well. Once this There are a number of important points illustrated in
is realized, dbms_output becomes the least important use this type specification.
for these object types. All of the procedures declared in this specification
I begin with the base object type, which I’ve named will become part of the definition of any new object
OutputChannel, as shown in Listing 1. I use Channel types declared using OutputChannel. This is the basic
instead of Stream because Oracle is already using the term meaning of inheritance: Subtypes begin with all of the
“Stream” for some very different functionality. The root variables, procedures, and functions declared in their
object type is declared as NOT SUBSTANTIABLE NOT parent. They can add new variables, procedures, and
FINAL. This means that I can’t actually create an instance functions, and they can override any procedures or
of an OutputChannel, but that I can define subtypes that functions in their parent—that is, implement them in
inherit from it. OutputChannel provides the template for some different way. For example, in OutputChannel
all of its subtypes. the writeLn procedure is self.write( pText || chr(13)
Even though I can’t create an instance of an ||chr(10)), even though the write procedure itself is
OutputChannel, I can have variables declared as not implemented. For most subtypes this will be
OutputChannel, and I can pass parameters of adequate, but when a different behavior is needed,
OutputChannel type. Any variable that’s of a type writeLn can be overridden, as you’ll see in the following
declared under OutputChannel can be stored in an DbmsOutputChannel type. Only procedures and
OutputChannel variable or parameter, and any operation functions declared as FINAL cannot be overridden.
declared in the OutputChannel class can be performed Secondly, the procedures declared as NOT
on descendent type variables without actually knowing INSTANTIABLE aren’t implemented in the TYPE
their specific type. BODY. They must be implemented in any subtype of
OutputChannel that’s declared as INSTANTIABLE.
Declaring the OutputChannel object type Because their presence is guaranteed in any subtype
I begin by declaring a VARRAY of INTEGER, to base my variable that can be assigned to an OutputChannel
state stack on: variable, PL/SQL allows those procedures to be invoked
regardless of which subtype is involved.
CREATE OR REPLACE TYPE INTEGERLIST
AS VARYING ARRAY (20000) OF INTEGER
Procedure foo( pChannel IN OUT NOCOPY OutputChannel)
is
This is a generic integer VARRAY that I use for a begin
wide variety of purposes. If I were declaring a VARRAY pChannel.writeLn(‘I wonder what my actual type is’);
end;
for use only as a stack for maintaining state in the
OutputChannel type, the size would be reduced by a OutputChannel doesn’t have a constructor. It’s not
factor of 100. instantiable, so it would seem pointless to have a
constructor, but even if I did declare a constructor, I’d still
Listing 1. OutputChannel object type specification. place any actual initialization in a separate procedure
(initializeStateStack in this example) so that it can be
CREATE OR REPLACE TYPE OUTPUTCHANNEL called by descendent types. Constructors aren’t inherited,
AS OBJECT
and I can find no mechanism that will allow a subtype
(
state_stack integerList, -- Varray of integer
constructor to invoke its parent constructor upon itself, so
member procedure close, if significant initialization is done within a constructor it
Wrapping dbms_output in an
OutputChannel subtype Listing 3. The ClobOutputChannel specification.
Now let’s define the simplest subtype, shown in Listing 2.
CREATE OR REPLACE
The specification for all subtypes will be very similar to TYPE CLOBOUTPUTCHANNEL UNDER OUTPUTCHANNEL
this, because all must implement the procedures declared (
the_clob clob,
as not instantiable in the parent type. constructor function ClobOutputChannel(
pClob in out nocopy clob) return self as result,
overriding member procedure write(pText in varchar2),
Listing 2. DbmsOutputChannel specification. overriding member procedure writeRaw( pRaw in raw),
-- and a function to retrieve the clob if not passed
-- by reference
CREATE OR REPLACE member function getClob return clob,
TYPE DBMSOUTPUTCHANNEL UNDER OUTPUTCHANNEL static procedure test
(
constructor function DbmsOutputChannel ) instantiable not final
return self as result, /
overriding member procedure write(pText in varchar2),
overriding member procedure
writeLn(pText in varchar2), Fill-in-the-blanks programming
overriding member procedure writeRaw(pRaw in raw),
overriding member procedure close, If you check the source code available in the Download,
static procedure test you’ll see how simple this is to implement. One of the
) instantiable not final
/ advantages of working with hierarchies of object types
is that the parent establishes a template. All the
The constructor needs no parameters because there’s subtype must do is fill in those portions of the template
nothing to initialize—its only content is a call to the that apply specifically to it. Think of it as “fill-in-the-
InitializeChannel procedure implemented by Channel. blanks” programming.
The Flush and Close procedures aren’t implemented since The constructor function takes a CLOB as a
with dbms_output they have no function—so the null parameter. Even though the NOCOPY hint is used, I
procedures implemented at the parent level are fine. haven’t found a way of enforcing that a temporary CLOB
WriteLn is implemented, overriding the writeLn be passed by reference, which is why the getClob function
procedure in the OutputChannel type, because has been added. Check the listings associated with this
dbms_output.put(pText ||chr(13)||chr(10)) isn’t the article for an illustration: If you create a temporary CLOB,
same as dbms_output.put_line(pText)—if you end a script pass the locator to the ClobOutputChannel constructor,
in SQL*Plus with a dbms_output.put, instead of put_line, and write to it using one of the ClobOutputChannel
the result will never be displayed, as you can see here: procedures; what you write won’t be visible to the
I
need to pivot data in a stock table. The structure of
The concept is straightforward, and the code is
this table is:
simple, as well. There is, however, a problem: This isn’t
CREATE TABLE stocktable ( a terribly efficient implementation. I’ll be comparing
ticker VARCHAR2(20), the performance of alternative approaches to solving
trade_date DATE,
open_price NUMBER, the pivot problem in this article. To do so, I’ll fill up the
close_price NUMBER stock table with 100,002 rows, as you can see in Listing
);
2. And with this data in place, it takes 36 seconds to
In other words, each row contains a ticker symbol, pivot the data using the good old cursor FOR loop in
the date of trade, the open price, and the close price on pivot_stock_data1. Given that my application will need
that day. I need to “pivot” this data, which in this case to pivot 10,000,000 rows of data, this performance is
means that I need to take a single row in stocktable and unacceptable. So what are my options?
transform it into two rows in the price table defined here:
Listing 2. Populating the stock table.
CREATE TABLE tickertable (
ticker VARCHAR2(20),
pricedate DATE, BEGIN
pricetype VARCHAR2(1), INSERT INTO stocktable
price NUMBER VALUES ('ORCL', SYSDATE, 13, 13.5);
);
INSERT INTO stocktable
VALUES ('MSFT', SYSDATE, 27, 27.04);
This pivot process shouldn’t conceptually cause any
FOR indx IN 1 .. 100000
reader of Oracle Professional to lie awake in bed at night. LOOP
There is, in fact, a very straightforward approach to INSERT INTO stocktable
VALUES ('STK' || indx, SYSDATE, indx, indx + 15);
performing this transformation, shown in Listing 1. What END LOOP;
could be simpler? Set up a cursor FOR loop to run COMMIT;
through the table and convert one row into two rows in END;
/
the tickertable.
Transformative functions to the rescue?
Listing 1. The traditional, aka “old fashioned,” approach to In the May 2002 issue of Oracle Professional (available on
pivoting the data. the Web site to subscribers), Bryn Llewellyn and I wrote
about the new table function capabilities in Oracle9i. A
PROCEDURE pivot_stock_data1 table function is a function that can be called in the FROM
IS
BEGIN clause of a SELECT statement. It accepts as its parameter a
FOR rec IN (SELECT * result set (rows and columns of data) in the form of a
FROM stocktable)
LOOP cursor variable, and returns a nested table.
You see, I’m not running this function in parallel. I’m mystock := stockpivot_nopl (curvar);
indx := mystock.FIRST;
calling it from my session just once, and I’m not going to
be doing anything with it until the function is completely LOOP
EXIT WHEN indx IS NULL;
finished. I have found, in fact, that in this situation, the
non-pipelined version of the function generally runs just a INSERT INTO tickertable
(ticker
bit more efficiently. The stockpivot_pl program is , pricedate
definitely and dramatically faster than the cursor FOR , pricetype
, price
loop approach, but the PIPELINED syntax doesn’t give )
VALUES (mystock (indx).ticker
you any advantage. , mystock (indx).pricedate
, mystock (indx).pricetype
, mystock (indx).price
);
Listing 4. A pipelined pivot function.
indx := mystock.NEXT (indx);
CREATE OR REPLACE FUNCTION stockpivot_pl ( END LOOP;
dataset refcur_pkg.refcur_t END;
)
RETURN tickertypeset PIPELINED
IS What about bulk processing?
out_obj tickertype := tickertype (NULL, NULL, NULL, NULL);
in_rec dataset%ROWTYPE;
Whenever you’re executing multiple rows of DML in a
BEGIN PL/SQL program (and you’re using Oracle8i 8.1.7 and
LOOP
FETCH dataset INTO in_rec; above), you should always consider FORALL as an option
EXIT WHEN dataset%NOTFOUND; for executing that DML. With FORALL, you can process
-- first row multiple DML statements within the SQL engine, cutting
out_obj.ticker := in_rec.ticker;
out_obj.pricetype := 'O'; down dramatically on “context switches” (between SQL
out_obj.price := in_rec.open_price; and PL/SQL), which can degrade performance.
out_obj.pricedate := in_rec.trade_date;
In fact, I imagine that many readers looked at the
PIPE ROW (out_obj);
code in Listing 5 and said to themselves, “What a cheater,
-- second row Steven is! He could make that program much faster by
out_obj.pricetype := 'C';
out_obj.price := in_rec.close_price; using FORALL.” Let’s see if that’s the case.
out_obj.pricedate := in_rec.trade_date; I’ll now take Listing 5 and change it to use FORALL.
PIPE ROW (out_obj); It’s not as easy as it first might seem. The code is shown in
END LOOP;
Listing 6. I populate the mystock nested table the same
CLOSE dataset; way that I did in Listing 4. Now, you might think I could
RETURN;
END; move immediately to a FORALL statement. That’s not
/ possible, unfortunately, because there’s a restriction in
FORALL: I cannot reference individual attributes or fields
One of the key reasons that the transformative
of a collection of records or objects.
function is so efficient has to do with the avoidance of
In other words, I can’t execute code that looks
relying on intermediate data structures in PL/SQL.
like this:
To drive that point home, consider the implementation
in Listing 5. In this program, I use the efficient FORALL indx IN
stockpivot_nopl to pivot the data, and then dump the l_tickertable.FIRST .. l_tickertable.LAST
INSERT INTO tickertable
data into a local nested table, mystock. I then use a FOR (ticker, price)
VALUES (
loop to perform my inserts. This program takes more l_tickertable (indx).ticker
than 43 seconds. It’s even slower than the cursor FOR , l_tickertable (indx).price);
loop approach!
So I either have to break up my single nested table
into four different nested tables (ugly!) or... I could take
Listing 5. Relying on intermediate data structures in PL/SQL. advantage of another really nice Oracle9i feature: INSERT
a record, as opposed to individual column values.
PROCEDURE intermediate_structure_pivot
/* I’ll go this second route, but in order to do that I’ll
Use a nested table to retrieve the pivoted data
and then transfer to the database table.
need to copy the nested table of objects into an associative
*/ array of records. Once I’ve done this, I can then use the
www.pinnaclepublishing.com Oracle Professional April 2004 9
FORALL statement. The result is a significant boost in FROM stocktable;
performance. I can pivot 100,002 rows in approximately 18 FOR indx IN l_stocktable.FIRST .. l_stocktable.LAST
LOOP
seconds. It’s not as fast as the transformative function, but l_index := l_tickertable.COUNT + 1;
it’s a major improvement over the other implementations l_tickertable (l_index).ticker :=
l_stocktable (indx).ticker;
we’ve explored. l_tickertable (l_index).pricedate :=
l_stocktable (indx).trade_date;
l_tickertable (l_index).pricetype := 'O';
l_tickertable (l_index).price :=
Listing 6. Using FORALL and an intermediate collection. l_stocktable (indx).open_price;
l_index := l_tickertable.COUNT + 1;
l_tickertable (l_index).ticker :=
PROCEDURE intermediate_forall_1 l_stocktable (indx).ticker;
IS l_tickertable (l_index).pricedate :=
curvar sys_refcursor; l_stocktable (indx).trade_date;
mystock tickertypeset := tickertypeset (); l_tickertable (l_index).pricetype := 'C';
l_tickertable (l_index).price :=
TYPE tickertable_tt IS TABLE OF tickertable%ROWTYPE l_stocktable (indx).open_price;
INDEX BY BINARY_INTEGER; END LOOP;
F
IRST, let’s define what a template is. The American 1. Select the Manage Templates option.
Heritage Dictionary defines a template as “a 2. Select either “From an existing database (structure
document or file having a preset format, used as a only)” or “From an existing database (structure as
starting point for a particular application so that the well as data).”
format does not have to be recreated each time it is used: 3. Enter the Source Database that’s to be cloned—this
a loan amortization template for a spreadsheet program.” can be local or remote if Net services are set up.
So, in Oracle9i, the same can be said about a database 4. Enter a name for your template along with
template. It’s basically a file containing a format of a a description.
database that can be used as a starting point to create a 5. Select either “Maintain the file locations” or
new database or clone (duplicate) a database on another “Convert the file locations to use OFA Structure.”
platform. The primary purpose of the 9i database
templates is to remove the complexity for creating This process now jars all of the data files for your
multiple similar databases and transition databases from database and places them in one large file (.dfj). The
one system to another. A database template is created by .dfj and .dbc files are created in the ORACLE_HOME\
using the 9i Database Configuration Assistant (DBCA). assistants\dbca\templates directory. You can now copy
these files to a new machine and have your data go along
Why use database templates? with it. This is great for moving from a test platform DB
Database templates allow DBAs to create new to a production environment, or from production to test
databases using predefined templates, or create new (when you want to test with real production data).
templates based on existing running databases that can
be used to create the databases at another time. This Note: Depending on the size of your database that’s to
serves as a robust new way of creating or duplicating be cloned, the size of the template files and the time
databases with or without the data. Here are some of required to build them may come into play. If the
the advantages: database is in the terabyte range, the .dfj file may
• It saves you time. If you use a template, you don’t become too large for the ORACLE_HOME and may be
have to define the database. difficult to move across a network to a target computer.
• By creating a template containing your database, you
can easily create a cloned database without specifying An example
parameters twice. I was required to create an additional test database for our
• The templates are easy to modify. You can change project on another computer. The target and source PCs
database options from the template settings. were running Oracle9i R2 Enterprise Edition on Windows
• Templates can be shared among several computers. 2000 with SP2.
Figure 1. Step 1: Select the Manage Templates option. Figure 4. Step 4: Select a new name for the new template.
Figure 2. Step 2: Select the option to create the template Figure 5. Step 5: Select the OFA option.
with data.
Figure 6. The
Summary box.
Know a clever shortcut? Have an idea for an article for Oracle Professional?
Visit www.pinnaclepublishing.com and click on “Write For Us” to submit your ideas.
F
IRST, let’s review some of the features that Transactions are very important in a database, but for
LOG4PLSQL offers. It’s easy to implement and use. logging problems, sometimes it’s necessary to log apart
You’ll find adaptation of various logging levels from the SQL transaction. Here are some examples of
depending on user requirements, without modifying the when this is desirable:
application code. It offers the capability to log in to a lot of • When you want to roll back a transaction, but not the
destinations—log file, NTEventLog, Mail, Syslog, JMS, log message at the same time
among others. It also offers the possibility to log apart • When your code is quite long and you want to view
from a transaction. its progress
LOG4PLSQL provides the same features in PL/SQL
that LOG4J does in Java (LOG4J is an Apache Jakarta LOG4PLSQL has two solutions to log apart from your
project—see http://jakarta.apache.org/log4j). transaction. The first one is the autonomous transaction to
After you download and install the software from log messages in a log table. The second one is DBMS_PIPE
http://log4plsql.sourceforge.net, logs are put in levels in to send the log message to the external LOG4J logger with
the PL/SQL code (function, procedure, package, and the log4plsql.backgroundProcess.
trigger). You can organize it in hierarchical sections The autonomous transaction feature is a full PL/SQL
(discussed later). Afterwards, it’s possible to choose the solution. It’s very easy to use the log message, as it’s
trace and the destination according to the need. Every immediately accessible by issuing a SELECT from the
parameter is stored out of the code. Here’s a sample: TLOG table or the VLOG view, as you can see here:
application. LOG
-----------------------------
• The INFO level is for informational messages that [12/06 12:54:01: 89][INFO]
highlight the progress of the application at a coarse- [SCOTT][out transaction][My information]
grained level.
• The WARN level is for potentially harmful situations. To use the second method, you’ll have to start a
• The ERROR level is for error events that might still background process (/cmd/startLog4JbackgroundProcess
allow the application to continue running. .bat or /cmd/startLog4JbackgroundProcess.sh). This
• The FATAL level is for very severe error events that small Java application reads a log message directly from a
might lead the application to abort. PIPE in the database and logs the message with LOG4J
capability. This method has two advantages:
Before logging in your code, it’s best to do a test using • There’s no added I/O in the database.
Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106
For access to current and archive content and source code, log in at www.pinnaclepublishing.com.
Phone: 800-493-4867 x.4209 or 312-960-4100 Copyright © 2004 by Lawrence Ragan Communications, Inc. All rights reserved. No part
Fax: 312-960-4106 of this periodical may be used or reproduced in any fashion whatsoever (except in the
Email: PinPub@Ragan.com case of brief quotations embodied in critical articles and reviews) without the prior
written consent of Lawrence Ragan Communications, Inc. Printed in the United States
of America.
Advertising: RogerS@Ragan.com
Oracle, Oracle 8i, Oracle 9i, PL/SQL, and SQL*Plus are trademarks or registered trademarks of
Editorial: FarionG@Ragan.com Oracle Corporation. Other brand and product names are trademarks or registered trademarks
of their respective holders. Oracle Professional is an independent publication not affiliated
Pinnacle Web Site: www.pinnaclepublishing.com with Oracle Corporation. Oracle Corporation is not responsible in any way for the editorial
policy or other contents of the publication.
Subscription rates This publication is intended as a general guide. It covers a highly technical and complex
subject and should not be used for making decisions concerning specific products or
applications. This publication is sold as is, without warranty of any kind, either express or
United States: One year (12 issues): $199; two years (24 issues): $348 implied, respecting the contents of this publication, including but not limited to implied
Other:* One year: $229; two years: $408 warranties for the publication, performance, quality, merchantability, or fitness for any particular
purpose. Lawrence Ragan Communications, Inc., shall not be liable to the purchaser or any
Single issue rate: other person or entity with respect to any liability, loss, or damage caused or alleged to be
caused directly or indirectly by this publication. Articles published in Oracle Professional
$27.50 ($32.50 outside United States)* reflect the views of their authors; they may or may not reflect the view of Lawrence Ragan
Communications, Inc. Inclusion of advertising inserts does not constitute an endorsement by
* Funds must be in U.S. currency. Lawrence Ragan Communications, Inc., or Oracle Professional.