You are on page 1of 16

Oracle

Solutions for High-End


Oracle® DBAs and Developers Professional

Extending PL/SQL
File I/O Capabilities
with Java
Steven Feuerstein
For many years, PL/SQL developers complained—with much justification—about
the UTL_FILE package, which was notable more for what it was missing than what it
contained. A number of deficiencies were corrected in Oracle9i Release 2, but there June 2004
are still problems. Steven Feuerstein provides the solutions, showing you how to get Volume 11, Number 6
answers from within PL/SQL.
1 Extending PL/SQL File I/O

O
RACLE9i Release 2 corrects many of the longstanding UTL_FILE Capabilities with Java
Steven Feuerstein
package deficiencies—now we can delete files from within native
PL/SQL code, as well as copy files, rename files, and get some 5 Pipelined Table Functions: An
attribute information. There’s still an awful lot of information “out there,” Example with Pipes and Views
however, of which UTL_FILE is painfully ignorant, and an awful lot of Gary Menchen
operations that it simply cannot perform. Consider the following list: 9 Oracle10g: Regular Expressions
• Can I read from a file? Write to a file? Is the named item a file or Parin Jhaveri
a directory?
• What is the parent directory of a file? 15 Tip: Current_schema
and user_tables
• What are the names of all of the files in a directory that match a Daniel Clamage
specified filter?
• Can I make a directory? 16 June 2004 Downloads
• What is the character that separates the directory from the file name?

This article explains how to get answers to all of these questions from
within PL/SQL. Well, to be perfectly honest, while you will indeed be able to Indicates accompanying files are available online at
call PL/SQL programs to get those answers, you’ll in fact be using Java classes www.pinnaclepublishing.com.
and PL/SQL wrappers on top of those classes to get the named File.separator that returns the value I desire.
job done. To take advantage of that method, I’ll need to create
By the way, if you’re not yet taking advantage of my own class, which I’ll call JFile. This class will, broadly,
Java in your PL/SQL development, and if you’re not contain a method (procedure or function) for each of
comfortable reading Java code and writing at least simple the File methods I want to use. My own method will
classes, then I urge you to buy a good Java book (such as instantiate an object of class File, and then apply the
Thinking in Java, by Bruce Eckels) and put aside a half day desired method to that object. These steps are shown
of your time for Java. here in my first, simplest JFile implementation:
The panic that some of us felt when Oracle first
announced support for Java in the database (“Oh my CREATE OR REPLACE AND RESOLVE
JAVA SOURCE NAMED "JFile" AS
gosh, is PL/SQL going away?”) has largely subsided. import java.io.File;
public class JFile {
Obviously, Oracle is very committed to PL/SQL. There
are, however, situations when PL/SQL quite simply won’t public static String separator (String fileName) {
File myFile = new File (fileName);
do the trick, but Java will. For such scenarios, you should return myFile.separator;
be ready to throw together a simple Java class, drop a }
}
PL/SQL wrapper on top of it, and solve your problems. /
That is, after all, the whole point to our work
as developers: Solve a problem, fulfill a business I can run this DDL statement directly in SQL*Plus to
requirement. define the class within the Oracle database. Once I’ve
You’ll find two files in the Download for this article: done that, I can create a function in PL/SQL that invokes
• JFile.java—A Java class that draws together various the separator method:
pieces of information about operating system files
and offers it through an API accessible from PL/SQL. CREATE OR REPLACE PACKAGE xfile
IS
• xfile.pkg—The PL/SQL package that wraps the JFile FUNCTION separator (FILE IN VARCHAR2)
RETURN VARCHAR2;
class. Stands for “eXtra stuff for FILEs.” END;
/
Let’s take a look at the different elements within CREATE OR REPLACE PACKAGE BODY xfile
the JFile/xfile partnership, from the simplest to the IS
FUNCTION separator (FILE IN VARCHAR2)
more complicated. RETURN VARCHAR2
AS
LANGUAGE JAVA
What is that operating system delimiter? NAME 'JFile.separator (java.lang.String)
It’s always struck me as more than a little bit odd that return java.lang.String';
END;
UTL_FILE isn’t able to parse a file specification that /
combines the directory and file name, as in:
Notice that my function implementation doesn’t
d:\temp\myfile.txt. contain any PL/SQL code; instead, it specifies the
language as Java and then provides a string that specifies
But sadly it’s the case that UTL_FILE doesn’t know
how to call the Java method. The number and datatypes
the character used by the operating system to separate
of my function parameters must map to the parameter
the two. And that’s why when we open a file, we have
list of the Java method.
to provide the location and name separately. (I’ve been
I won’t review the various mappings in this article;
told that Oracle bought the underlying operating-system
I suggest you check the Oracle documentation for the
independent file I/O technology that first defined
details. Within this article, however, I will use the
UTL_FILE from a third party; I hope it was the only
mappings from Table 1.
option available at the time. That would be a good
excuse, anyway.)
Well, suppose that I really, really need to be able to Table 1. Datatype mappings.
obtain that separator in my PL/SQL code. What’s a
developer to do? PL/SQL datatype Java datatype
VARCHAR2 java.lang.string
Just like Oracle has built-in packages, Java has INTEGER float
“foundation classes”—lots of them. So if I’m going to BOOLEAN int
use Java to solve my problem, my first step is to find a
foundation class that already gives me what I need. I bet you’re surprised by that last one; for some
There are many different classes that perform file I/O. reason, the BOOLEAN datatype of PL/SQL doesn’t map
A good place to start is the File class. And, in fact, when I directly to the Java boolean type. This complicates some
check out the class definition, I find that there’s a method of our work, as you’ll see.

2 Oracle Professional June 2004 www.pinnaclepublishing.com


So, once I’ve loaded my class and defined my Java wrapper (I call it “i_isdirectory” for “internal
package, I can then obtain my file separator in “normal” is_directory program”) inside the package body.
PL/SQL code as follows: I then create a “public” function, isdirectory, that calls
the internal program and does the necessary translation:
BEGIN
IF xfile.separator (file_in) = '\'
RETURN I_isdirectory (FILE) = 1;
THEN
-- Must be using Windows...
That’s better, but I still have a problem: I’ve hard-
Do I have a directory or a file? coded the 1-0 scheme within both my Java class and
Any self-respecting file I/O utility would help me answer my PL/SQL package. If I’m going to have hard-coded
this question. Let’s see how I can get there with Java and values, I should at least make sure that they’re defined
PL/SQL. Once again, the code in Java is very simple: in just one place and reused from there. Let’s see how
I can accomplish this goal.
CREATE OR REPLACE AND RESOLVE
JAVA SOURCE NAMED "JFile" AS
First, I’ll add methods that encapsulate the
import java.io.File; TRUE/FALSE values that the other methods will return
public class JFile {
as follows:
public static int isDirectory (String fileName) {
File myFile = new File (fileName); CREATE OR REPLACE AND RESOLVE
boolean retval = myFile.isDirectory(); JAVA SOURCE NAMED "JFile" AS
if (retval) return 1; else return 0; import java.io.File;
}
public class JFile {
}
/
public static int tVal () { return 1; };
public static int fVal () { return 0; };
But now we run into a small problem. As mentioned
earlier, a Java boolean doesn’t, unfortunately, map directly Then I call the appropriate methods in my
to a PL/SQL BOOLEAN. So I’ve decided to return a 1 if JFile.isdirectory method:
the string points to a directory, 0 otherwise.
Here’s the code that exposes this useful information public static int isDirectory (String fileName) {
File myFile = new File (fileName);
within PL/SQL: boolean retval = myFile.isDirectory();
if (retval) return tVal; else return fVal;
}
CREATE OR REPLACE PACKAGE xfile
IS
FUNCTION isdirectory (FILE IN VARCHAR2) That takes care of the Java side of things; time to
RETURN BOOLEAN;
END; shift attention to my PL/SQL package. Remember: The
/
goal is to avoid repeating the hard-coded values from
CREATE OR REPLACE PACKAGE BODY xfile Java. So I’ll create PL/SQL functions to retrieve those
IS
FUNCTION i_isdirectory (FILE IN VARCHAR2) values via the tVal and fVal methods. I define global
RETURN NUMBER private variables for the true and false values, and then
AS
LANGUAGE JAVA set them just once in the initialization section of the
NAME 'JFile.isDirectory (java.lang.String) package. No more hard-coded values!
return int';

FUNCTION isdirectory (FILE IN VARCHAR2) CREATE OR REPLACE PACKAGE BODY xfile


RETURN BOOLEAN IS
AS g_true INTEGER;
BEGIN g_false INTEGER;
RETURN I_isdirectory (FILE) = 1;
END; FUNCTION tval RETURN NUMBER
AS LANGUAGE JAVA
END; NAME 'JFile.tVal () return int';
/
FUNCTION fval RETURN NUMBER
AS LANGUAGE JAVA
This is a bit more complicated than the separator- NAME 'JFile.fVal () return int';
related code. I decided that I didn’t want to force a user
FUNCTION isdirectory (FILE IN VARCHAR2)
of xfile.isdirectory to write code like this: RETURN BOOLEAN
AS
BEGIN
IF xfile.isdirectory (l_file_or_dir) = 1 THEN ...
RETURN iisdirectory (FILE) = g_true;
END;
That’s very ugly, hard-coded software. Not only is it BEGIN
ugly, but the person writing the PL/SQL code must know g_true := tval;
g_false := fval;
about the values for true and false embedded within a END;
Java class. Rather than endure such pain, I’ve hidden the /

www.pinnaclepublishing.com Oracle Professional June 2004 3


Obtaining directory contents files(files.LAST) :=
SUBSTR (file_list,
One of my favorite features of JFile is its ability to return start_pos,
next_delim - start_pos);
a list of files found in a directory. It accomplishes this start_pos := next_delim + 1;
feat by calling the File.list() method; if the string you END LOOP;
END;
used to construct a new File object is the name of a
directory, it returns a String array of file names found in From there, it’s all just fun and games with PL/SQL.
that directory. Let’s see how I can make this information You’ll find in the xfile package the programs listed in
available in PL/SQL. I create a String method called Table 2, built on top of getDirContents.
dirContents, shown here:

public static String dirContents (String dir) { Table 2. Some of the programs found in the xfile package.
File myDir = new File (dir);
String[] filesList = myDir.list();
Program Description
String contents = new String();
getDirContents Allows the user to pass a filter (such as “*.tmp”
for (int i = 0; i < filesList.length; i++) (the filter version) or “%.tmp”) and retrieve only files that match
contents = contents + listDelimiter + filesList[i]; the filter. The underscore character ( _ ) will be
return contents; treated as a single-character wildcard, following
}
the SQL standard.

This method instantiates a File object called myDir showDirContents Displays all of the files found in the specified
and then assigns the myDir.list() to a String array called directory, matching your filter.
filesList. I then use a Java “for loop” to concatenate
Chgext Changes the extension of the specified files.
each of the files into a single String, separated by the
listDelimiter, and return that String. Over on the You’ll also find in the xfile package all of the entry
PL/SQL side of the world, I’ll create a wrapper that points of the UTL_FILE package, such as FOPEN and
calls this method: PUT_LINE. I added those so that you can avoid the use
FUNCTION dirContents (dir IN VARCHAR2)
of UTL_FILE for anything but declarations of file handles
RETURN VARCHAR2 as UTL_FILE.FILE_TYPE.
AS LANGUAGE JAVA
NAME 'JFile.dirContents (java.lang.String)
return java.lang.String'; Make sure you have at least two hammers at
your disposal
But what am I to do with this string? Let’s build
You know the saying: If the only tool you have is a
some additional code elements on top of my wrapper
hammer, then everything looks like a nail. PL/SQL is
functions to make the information more developer-
an absolutely wonderful language (I’d be one miserable
friendly. First, I’d like to let users of xfile manipulate files
fellow if I didn’t believe that, given how much of my
either as a string list or as a nested table (much more
life is devoted to PL/SQL), but we must be honest with
structured data; easier to scan and manipulate). So I’ll
ourselves: If it’s the only tool we’ve got, we’re going
define a nested table type as follows:
to end up with a lot of bent nails and sore thumbs.
CREATE TYPE file_list_t IS TABLE OF VARCHAR2(2000); PL/SQL simply doesn’t offer natively the full range of
/ functionality needed for general-purpose programming.
In such situations, with file I/O being a pretty
And then I define a procedure to return the files good example, it’s good to have another hammer in the
in a directory in a nested table of this type. Note the toolbox, perhaps with a different heft and shape. In the
call to the dirContents wrapper function and also the case of the Oracle RDBMS, Java fits the bill. Consequently,
reference to g_listdelim, which contains the delimiter all PL/SQL developers should be conversant with Java
passed back from JFile (just like the numeric values for basics, and capable of writing the simple classes necessary
TRUE and FALSE): to solve their problems. ▲
PROCEDURE getDirContents (
dir IN VARCHAR2, 406FEUER.ZIP at www.pinnaclepublishing.com
files IN OUT file_list_t)
IS
file_list VARCHAR2(32767); Steven Feuerstein is considered one of the world’s leading experts on the
next_delim PLS_INTEGER; Oracle PL/SQL language, having written nine books on PL/SQL, including
start_pos PLS_INTEGER := 1;
BEGIN Oracle PL/SQL Programming and Oracle PL/SQL Best Practices (all from
files.DELETE; O’Reilly Media). Steven has been developing software since 1980 and
file_list := dirContents (dir);
LOOP serves as a senior technology advisor to Quest Software. His current
next_delim := projects include Swyg (www.Swyg.com) and the Refuser Solidarity
INSTR (file_list, g_listdelim, start_pos);
EXIT WHEN next_delim = 0;
Network (www.refusersolidarity.net), which supports the Israeli military
files.EXTEND; refuser movement. steven@stevenfeuerstein.com.

4 Oracle Professional June 2004 www.pinnaclepublishing.com


Oracle
Professional

Pipelined Table Functions: An


Example with Pipes and Views
Gary Menchen
Pipelined table functions can be used to turn any kind of Oracle provides a default column name of column_value.
data into a virtual table. In this article, Gary Menchen wraps It would be more common to define a TYPE, and then a
a pipelined table function around a pipe, and then creates a COLLECTION of that type.
view on the table function. The result is a pipe that’s accessed Incidentally, column_value can be used to access the
exactly like a table, including standard security. Add an contents of the column as well.
instead-of trigger for inserts into that view and you have a
complete SQL-based interface for pipes. SQL> select * from table(simpleTextTbl(25))
2 where instr(column_value,'5') > 0
3 /

O
RACLE’S pipelined table functions allow any form
COLUMN_VALUE
of consistently structured data to be returned as ------------------------------------------------
a collection type. Since collection types can be I am nested table row 5
I am nested table row 15
cast to a TABLE, this provides a very convenient method I am nested table row 25
of retrieving data that isn’t normally accessible via any
SQL>
SQL query.
Let’s look at a rudimentary pipelined function (see Now let’s take a look at a more useful example of
Listing 1). We’ll use, as a collection type, a TABLE of pipelined functions.
VARCHAR2, but we could just as well have used a
VARRAY of VARCHAR2. Pipes and pipelined functions
Oracle pipes and pipelined table functions share a bit
Listing 1. A simple pipelined function. more than just a common syllable. Both are described in
Oracle documentation as producing data that’s meant to
CREATE OR REPLACE be consumed. For an Oracle pipe, that means that when
TYPE TEXTTBL
AS TABLE OF VARCHAR2(2048) data is read from a pipe it’s gone forever; for pipelined
/ table functions, it means that the results are meant to be
CREATE OR REPLACE
function SIMPLETEXTTBL(pRows in integer) consumed in a similar way. Oracle doesn’t buffer the
return TextTbl Pipelined
-- returns a nested table
contents of a pipelined function unless it’s marked as
is Deterministic. (This raises an interesting possibility—
begin
for i in 1..pRows loop should all of the individual configuration elements in
pipe row ('I am nested table row '||to_Char(i)); an application be placed in a single row returned by a
end loop;
return; deterministic pipelined function, ready to join to any
end; query in the same manner DUAL is?)
/

This is accessed by casting to a table. A brief review of pipes


Pipes are used to feed data across sessions. Data is
SQL> select * from table(simpleTextTbl(3)) ; available to be read as soon as it’s placed in the pipe (it
COLUMN_VALUE
doesn’t require a commit to make it accessible to another
------------------------------------------------ instance)—a very powerful feature in the days before
I am nested table row 1
I am nested table row 2 autonomous transactions.
I am nested table row 3 There are two dimensions to the definition of a pipe.
SQL> First, a pipe may be EXPLICIT or IMPLICIT. An explicit
pipe is one that’s created via a call to the dbms_pipe
Here the collection was of a scalar type, a .create_pipe function. An explicit pipe exists until it’s
VARCHAR2; since this is essentially an unnamed column, removed by a call to dbms_pipe.remove_pipe or the

www.pinnaclepublishing.com Oracle Professional June 2004 5


instance is shut down. This has the potential to needlessly return PipedTextTbl pipelined;
procedure WriteToPipe(pPipeName in varchar2,
consume space in the SGA. An implicit pipe is created by pText in varchar2);
END; -- Package spec
writing data to it, and will be removed by Oracle when /
it’s empty—but not necessarily immediately.
CREATE OR REPLACE
The second dimension is PUBLIC or PRIVATE. When PACKAGE BODY PIPELINED
an explicit pipe is created, there’s an optional Boolean IS
pvSessionId number;
parameter to identify it as public or private—the default pvSeq integer;
pvSid integer;
is true: private. All implicit pipes are public. A public pipe
can be read from any schema in the database that has the procedure WriteToPipe(pPipeName in varchar2,
pText in varchar2)
EXECUTE privilege on the dbms_pipe package. is
Since pipe contents are permanently gone once result integer;
begin
read, it seems to me that a public pipe is only useful for dbms_pipe.pack_message(pText);
result := dbms_pipe.send_message(pPipeName,0);
debugging or for non-essential messages—rather like a if result <> 0 then
protocol that doesn’t guarantee message delivery. The raise_application_error(-20001, 'Unable to write '
|| 'to pipe: response code: ' || to_char(result));
only security is in obscurity—keeping the name of the end if;
pipe secret—and pipe names are available to anyone end;

with access to the SYS.V_$DB_PIPES view. Some function readPipe(pPipeName in varchar2)


return PipedTextTbl pipelined
assistance is provided by the dbms_pipe.unique_session_ is
name function that returns a consistent value unique for result integer;
vBuffer varchar2(2000);
each schema. begin
while true loop
In the following example I’ll use an implicit public result := dbms_pipe.receive_message(pPipename,0);
pipe, since the point of the example is to illustrate exit when result <> 0;
dbms_pipe.unpack_message(vBuffer);
accessing pipe content through a pipelined table pvSeq := pvSeq + 1;
function—and then to wrap that in a view. While different pipe row ( new PipedText( pvSessionId,sysdate,
pvSeq, vBuffer) );
data types can be sent through a pipe, I’m going to end loop;
return;
assume that simple text, line by line, is to be processed. end readPipe;
When processing by the pipelined table function, I’ll add begin
-- package initialization
columns for session id, date, and sequence. pvSessionId := sys_context('userenv','sessionid');
The first step is to define a TYPE for the rows to be select sid into pvSid from v$session
where audsid = pvSessionId;
returned by the pipelined function, and then a collection pvSeq := 0;
END;
type (see Listing 2). /

Listing 2. Declaring an object type and a collection type for the Accessing the data through the pipelined function is
pipelined function. simply a matter of casting it to a table—see Listing 4.

CREATE OR REPLACE
TYPE PIPEDTEXT Listing 4. Reading data from a pipe in a query.
AS OBJECT
(
SQL> begin
sessionid number,
2 for i in 1..3 loop
date_sent date,
3 pipelined.writeToPipe('gmenchen','Row number '
seq number,
4 || to_char(i));
text varchar2(2000) 5 end loop;
) 6 end;
/ 7 /
CREATE OR REPLACE
TYPE PIPEDTEXTTBL PL/SQL procedure successfully completed.
AS TABLE OF "PIPES"."PIPEDTEXT"
/
SQL> select text
2 from table(pipelined.readPipe('gmenchen'))
Next, I’ll declare a package to hold the pipelined 3 /
function itself (see Listing 3). As a convenience, I’ve
TEXT
included a procedure that will send some data down the ---------------------------------------------------
pipe to be read. Row number 1
Row number 2
Row number 3

Listing 3. Declaring the pipelined package. SQL>

CREATE OR REPLACE Creating a view from a pipelined function


PACKAGE PIPELINED
IS
Creating a view to access the pipelined function is simply
function readPipe(pPipeName in varchar2) a matter of beginning the query on the pipelined table

6 Oracle Professional June 2004 www.pinnaclepublishing.com


function with CREATE OR REPLACE VIEW: and 10g as well, so we must follow the approach
described here:
SQL> create or replace view pipeview as
2 select *
CREATE OR REPLACE VIEW PIPEVIEW
3 from table( pipelined.readPipe('gmenchen'))
AS
4 /
select * from table( cast(
pipelined.readPipe('gmenchen' ) as pipedTextTbl))
View created.

You can then retrieve the contents of the pipe by One of the implications of this is that we can
selecting from the view: provide much more flexible security on pipes than is
available when using just PL/SQL to access pipe content.
SQL> select seq,sessionid,date_sent We can, for example, create a private pipe, then create a
2, substr(text,1,20) text view on the pipe content through a pipelined procedure,
3 from pipeview
4 / and make standard grants on that view to other schemas
SEQ SESSIONID DATE_SENT TEXT or roles. This would provide the same level of security
---------- ---------- --------- -------------------- on pipes that’s available for tables.
7 2165 10-APR-04 Row number 1
8 2165 10-APR-04 Row number 2
9 2165 10-APR-04 Row number 3 Using an INSTEAD OF trigger to insert data
into a pipe
However, I found I was unable to make a grant on the Another interesting result of having a view on the
view to a role or to another schema (see Listing 5). contents of the pipe is that we can use an INSTEAD
OF trigger on that view to insert data into the pipe:
Listing 5. Trying to grant SELECT on the view.
CREATE OR REPLACE TRIGGER PIPEVIEW_INS
INSTEAD OF
SQL> grant execute on pipedtext to public INSERT
2 / ON PIPEVIEW
Grant succeeded. REFERENCING NEW AS NEW OLD AS OLD
SQL> grant execute on pipedTextTbl to public begin
2 / pipelined.writeToPipe('gmenchen',:new.text);
Grant succeeded. end;
SQL> grant execute on pipelined to public /
2 /
Grant succeeded.
SQL> grant select on pipeview to public We can insert into the view specifying the text
2 /
grant select on pipeview to public column, as in the following:
*
ERROR at line 1: sql> insert into pipeview(text) values('hello world');
ORA-22905: cannot access rows from a 1 row created.
non-nested table item sql> select text from pipeview
2 /
TEXT
Creating a view of a pipelined table function -------------------------------------------------------
that’s grantable hello world
I included grants on every possible dependent object
(as you can see from Listing 5) and also tried various Or we can use a values clause that has contents for all
permutations, including making the view an object view, of the columns in the view:
but with the same results. Finally, I stumbled across an
sql> insert into pipeview(sessionid,date_sent,seq,text)
example in the “PL/SQL Collections and Record Types” 2 values (1, sysdate,1,'hello #2');
section of PL/SQL User’s Guide and Reference and found
1 row created.
the following statement:
sql> select sessionid,date_sent
2 ,seq,substr(text,1,24) text
To perform DML operations on a PL/SQL nested table, 3 from pipeview
4 /
use the operators TABLE and CAST.
SESSIONID DATE_SENT SEQ TEXT
---------- --------- ---------- ----------------------
Clearly there’s a fundamental difference between 1909 21-MAR-04 3 hello #2
creating a view and selecting from it in one schema,
and making a grant on that view to a role or to another Because we’re using an INSTEAD OF trigger, it
schema. It seems that when you use the TABLE doesn’t really matter that we’re including all of the
operator by itself, it causes the resulting recordset to columns in the view—only the TEXT column is processed
lose its tight identification with the collection type within the trigger; the other columns are populated by the
defined at the schema level, and the exact form of the WriteToPipe procedure called from the trigger body.
table is determined at runtime. This is true with Oracle9i We can now grant INSERT on this view, and allow

www.pinnaclepublishing.com Oracle Professional June 2004 7


another schema—or holders of a role—to insert data into return;
end allObjects;
our pipe, even if it’s private. function getCtr return number
is
begin
Other uses of pipelined table functions return pvCtr;
end;
Pipes are a natural use of pipelined table functions END;
since the data in pipes is normally only available within /

PL/SQL. By combining a pipelined function with a view,


The answer is surprisingly good news. Oracle sends
we’ve been able to construct an entirely new interface for
that data along as soon as it’s “piped.”
pipes that’s easier to use than the conventional interface
and has more flexible security. SQL> select * from table(pipedObjects.allObjects)
A different kind of use for pipelined table functions 2 where rownum < 3
3 /
is in assembling rows of data that, using conventional
SQL, would be much more difficult to assemble. I expect COLUMN_VALUE
---------------------------------------------------
that we’ve all seen examples, particularly in reports, of /1005bd30_LnkdConstant
convoluted and deeply nested queries, or of temporary /10076b23_OraCustomDatumClosur

tables used to manipulate data into a structure that can SQL> select pipedObjects.getCtr from dual
2 /
be used to produce rows in a report. Pivot queries,
collapsing and expanding the level of detail (week, GETCTR
----------
month, quarter, fiscal year combined with department, 2
division, corporation, and so on) usually result in either
creating many new database objects or extensive use of Are pipelined table functions ready for
dynamic SQL that can be difficult for others to modify. prime time?
Inside a pipelined function, one can assemble a VARRAY Anyone planning to put pipelined table functions
or NESTED TABLE of the rows for a report, using into production does need to review the various bug
multiple passes to populate the columns. reports on Oracle’s Web site—in particular, Bug 2883743
(default parameters in pipelined table functions cause
Implications of using queries inside pipelined memory leak) and Bug 2403065 (a problem with the
table functions no_data_needed exception). The most serious problems
If it’s likely that data will be retrieved from a pipelined are caused when the collection type returned by the
table function using a where clause: function is defined inside a package rather than at the
schema level—it may compile, but it’s certain to cause
Select * from table( <my_pipelined_function>) serious problems in use. The memory leak problem
where <expression>
occurs when the parameters of a pipelined function have
then what happens to the processing of the cursor inside default values—to avoid the issue, don’t use default
the pipelined function? Let’s look at a package that values. This Bug was recently reported as being fixed in
contains a trivial pipelined function that tracks the Oracle version 10.1.0, but I haven’t confirmed Oracle’s
iterations inside the embedded cursor—see Listing 6. claim yet.
Pipelined table functions are ideal for use in
providing SQL access to data that’s normally only
Listing 6. Tracking fetches inside a cursor. available using PL/SQL, such as the interface to
dbms_pipes described in this article. A second use is
CREATE OR REPLACE
PACKAGE PIPEDOBJECTS in assembling rows of data where there are advantages
IS of speed or simplicity in collecting the data piece by
function AllObjects return textTbl pipelined; piece rather than in a single query. And finally, wrapping
function getCtr return number; a pipelined table function in a view opens up new
END; -- Package spec
/ possibilities for the exchange of data between Oracle’s
SQL and PL/SQL contexts. ▲
CREATE OR REPLACE
PACKAGE BODY PIPEDOBJECTS 406MENCHEN.TXT at www.pinnaclepublishing.com
IS
pvCtr integer := 0;
function allObjects return textTbl pipelined Gary Menchen is a senior programmer analyst at Dartmouth College
is
in Hanover, New Hampshire. He’s been writing articles on computer
begin
for rec in (select object_name from all_objects) programming for 20 years on subjects ranging from PL/SQL to Clipper,
loop C, Assembly Language, and—ancient history—dBase. At the moment,
pipe row (rec.object_name);
pvCtr := pvCtr + 1; he’s particularly interested in document generation using PL/SQL.
end loop; Gary.E.Menchen@Dartmouth.Edu.

8 Oracle Professional June 2004 www.pinnaclepublishing.com


Oracle
Professional

Oracle10g: Regular Expressions


Parin Jhaveri
Oracle has enhanced SQL with several new features in version SQL> DESC emp
Name Null? Type
10g. In this article, Parin Jhaveri examines regular expressions ----------------------------- -------- -------------
EMP_ID NOT NULL NUMBER(10)
in Oracle Database version 10g. SSN NOT NULL CHAR(9)
EMAIL VARCHAR2(50)

R

EGULAR expressions are a tool for searching and
manipulating textual data. We use regular
Once you have the check constraint in place,
expressions all the time—even if we don’t realize
there’s no need to validate the e-mail address in the
it. If you use a search engine on the Web, you’re already
front-end code.
using regular expressions. Most word processors and
modern languages such as shell, awk, Perl, and Java ALTER TABLE emp MODIFY
have built-in support for regular expressions. Regular (email varchar2(50), check
(regexp_like(email,
expressions are a set of metacharacters, or a pattern, '[:alnum:]*\@[:alnum:]\.[com|org|edu]')));
that represents chunks of text. For example, a pattern like
--valid email address
“[0-9]?[0-9]:[0-9][0-9]:[0-9][0-9] [A|a|P|p][M\m]” can SQL> INSERT INTO emp (person_id,ssn,email)
be used to represent any text like “12:01:01 AM” or VALUES (1,'999999999','email@domain.com');
1 row created.
“1:10:10 pm” or even “5:10:10 pM”. Regular expressions
are very powerful. A simple regular expression can --invalid email address does not contain '@'
SQL> INSERT INTO emp (person_id,ssn,email)
replace hundreds of lines of code. You might use regular VALUES (1,'999999999','emaildomain.com');
expressions to parse a large amount of text data, or to ERROR at line 1:
ORA-02290: check constraint (SYS.SYS_C005315) violated
search textual data using a pattern, or perhaps to verify
the format of a Social Security number. Once you start By moving the logic to the centralized place in the
using it, you’ll become addicted to it. database, you get the obvious maintenance advantage
of not having to duplicate your validation in multiple
The need for regular expressions in the database front ends.
Regular expressions (regexp) can be very useful in The Social Security number (SSN), which is stored as
Web applications. Most Web applications have a a CHAR(9) in the emp table, could also be presented in
database at the back end to store data. Inclusion of “999-99-9999” format by creating a simple view with a
regular expressions in the database eliminates the regular expression function as shown here:
need to duplicate logic in all front-end (or middle-tier)
applications. For example, regular expressions can be CREATE OR REPLACE VIEW emp_v AS
used to validate e-mail addresses using a pattern that SELECT regexp_replace(ssn
,'([0-9]{3})([0-9]{2})([0-9]{3})','\1-\2-\3') ssn, …
might contain characters like “.” and “@” and text like FROM emp;
“com” or “org” at the end.
Consider the scenario where you have a central Once you have the view in place, all of your
employee database that stores personal information. applications can simply query the view instead of
The database is a central back end for your Web retrieving and formatting Social Security numbers on
applications, ERP application, and reporting applications. the front end. Effectively, all of your applications need
You need to validate employee e-mail addresses entered not know how the Social Security number is stored in
from all of your applications. One way to achieve this the database.
is to put the verification logic on all of your front-end
applications separately, meaning you need to put the Regular expressions in Oracle10g
validation logic in the Web applications and the ERP Oracle Database 10g includes support for IEEE/POSIX
application. On the other hand, this could be achieved standard native regular expressions in the SQL. Oracle
by using a simple regular expression in the database. regular expression capability is compatible with that
By using a new regular expression SQL function, you available in programming environments such as Unix,
can add a check constraint on the email column as Perl, and Java. Programmers familiar with regular
shown here: expressions have always used external programs outside

www.pinnaclepublishing.com Oracle Professional June 2004 9


Oracle to achieve search and replace functionality. For
Table 2. Character classes.
example, you can write a Perl script and call it from a
PL/SQL procedure using extproc. However, defining Class Meaning
and configuring these interfaces always adds some [:alnum:] All alphanumeric characters
extra work. [:alpha:] All alphabetic characters
Before Oracle Database version 10g, Oracle supported [:cntrl:] All control characters
[:digit:] All numeric digits
regular expressions through the package owa_pattern, [:lower:] All lowercase alphabetic characters
which went undiscovered by many programmers. There [:print:] All printable characters
are some issues with owa_pattern—it’s not compatible [:punct:] All punctuation characters
with POSIX regular expressions, it follows different [:space:] All space characters
[:upper:] All uppercase alphabetic characters
syntax, and it has several limitations. With the
introduction of 10g, regular expressions become part of
the database SQL engine. Oracle’s implementation is Tip: Oracle stores characterset definitions in .nlb files in
locale-sensitive and supports multilingual queries. the $ORACLE_HOME/nls/data directory. Characterset
Table 1 lists all operators supported by Oracle. Table 2 definition files also define the classification (for example,
lists all character classes (a group of characters) supported Letter, Control, Space) for each character defined in the
by Oracle. Metacharacters and character classes listed set. You can view the definition of an existing character
in Tables 1 and 2 are defined by the database character set, or define a new locale using the Oracle “locale
set and national character set. This means ‘.’ (PERIOD – builder” utility. On Unix, see $ORACLE_HOME/ocommon/
match any character) matches to any character in the nls/lbuilder. On Windows, it’s %ORACLE_HOME%\
database characterset regardless of single-byte or multi- ocommon\nls\lbulider.bat (on Windows, I was able to find
bytes. Similarly, the character class [:alpha:] defines all a shortcut for the locale builder under “Configuration and
alphabetical characters in the characterset and is portable Migration Tools”).
across all database character sets.

Table 1. Regular expression operators in Oracle.

Operator Description Example


. Matches any character (except NULL).

* Matches zero or more occurrences. a* matches a zero or more times.

+ Matches one or more occurrences. a+ matches a one or more times.

? Matches zero or one occurrence. a? matches a zero or one time.

{m} Matches exactly m occurrences. a{3} matches aaa.

{m,} Matches at least m occurrences. a{3,} matches aaa, aaaa, aaaaa, and so forth.

{m,n} Matches at least m but no more than n occurrences. a{3,5} matches aaa, aaaa, or aaaaa.

| Alternation operator. a|b matches either a or b.

$ Matches the end-of-line character. b$ matches lines ending with b.

^ Matches the beginning-of-line character. ^a matches lines beginning with a.

[] Matches any character specified within brackets. [abc] matches a, b, or c.

[^] Matches any character except for the expressions represented in the list. [^abc] matches any character except a, b, and c.

(..) Group operator to identify a sub-expression.

\n Matches the nth sub-expression enclosed between parentheses \1 matches the first sub-expression enclosed between
preceding the \n. parentheses before \1.

[::] Specifies character classes (for example, [:alpha:]). It matches any [:alpha:] matches any alphabetical character in the
character within the character class. database character set. For US7ASCII, it matches any
character from a to z or A to Z.

[..] Specifies one collation element, and can be a multi-character element. [.ch.] in Spanish specifies one collation element.

[==] Equivalence class operator. [=a=] matches all characters with base letter a, such as
a, á, à, â, ã, ä, and å.

10 Oracle Professional June 2004 www.pinnaclepublishing.com


Oracle has implemented regular expressions through • pattern is the regular expression to search within the source_string.
• start_position specifies the position within the source_string where
four new SQL functions. Following is the list of new SQL
the search should begin. The default is 1, meaning the beginning of
functions available with Oracle Database 10g (source: SQL the source_string.
Reference Oracle 10g). All four functions are extensions to • occurrence indicates which occurrence to search for. The default is 1,
the existing SQL functions. I’ll describe these functions in meaning the first occurrence.
• match_parameters* is an optional parameter that lets you change
the next sections of the article.
the default behavior of REGEXP_SUBSTR.

Function:
Return value:
REGEXP_LIKE
Returns the matched portion of the pattern from the string.

Signature:
REGEXP_LIKE
Function:
(source_string REGEXP_REPLACE
,pattern
[,match_parameters])
Signature:
REGEXP_REPLACE
Parameters: (source_string
• source_string specifies source data to be scanned. ,pattern
[,replace_string
• pattern is the regular expression to search within the source_string. [,position
• match_parameters* is an optional parameter that lets you change [,occurrence
the default behavior of REGEXP_LIKE. [,match_parameter ]]]])

Return value: Parameters:


Returns TRUE or FALSE. • source_string specifies the source data to be scanned.
• pattern is the regular expression to search within the source_string.
Function: • replace_string is the regular expression to replace the matched
REGEXP_INSTR portion within the source_string.
• position specifies the position within the source_string where the
search should begin. The default is 1, meaning the beginning of the
Signature: source_string.
REGEXP_INSTR
(source_string • occurrence indicates which occurrence to search for. The default is 1,
,pattern meaning the first occurrence.
[,start_position • match_parameters* is an optional parameter that lets you change
[,occurrence the default behavior of REGEXP_REPLACE.
[,return_option
[,match_parameter ]]]])
Return value:
Parameters: Returns the replaced string.
• source_string specifies the source data to be scanned.
• pattern is the regular expression to search within the source_string. Note that match_parameters is an optional parameter
• start_position specifies the position within the source_string where
that lets you change the default behavior of regular
the search should begin. The default is 1, meaning the beginning of
the source_string. expression functions. You can specify one or more of the
• occurrence indicates which occurrence to search for. The default is 1, following values:
meaning the first occurrence. • i for case-insensitive searching. Default case sensitivity
• return_option lets you specify what will be returned:
is determined by the NLS_SORT parameter.
• If you specify 0, Oracle returns the position of the first
character of the occurrence. This is the default. • c for case-sensitive searching. Default case sensitivity
• If you specify 1, Oracle returns the position of the character is determined by the NLS_SORT parameter.
following the occurrence. • n to allow ‘.’ (PERIOD – match any character) to
• match_parameters* is an optional parameter that lets you change
match the newline character. By default, PERIOD (‘.’)
the default behavior of REGEXP_INSTR.
does not match the newline character.
Return value: • m to specify that source_string will be treated as
Returns the beginning position of the pattern within the string. multiple lines. By default, source_string is treated as
a single line.
Function:
REGEXP_SUBSTR The functions use the last value when contradictory
values are supplied together. For example, ic will search
Signature:
REGEXP_SUBSTR for case-sensitive patterns.
(source_string
,pattern
[,start_position REGEXP_LIKE
[,occurrence REGEXP_LIKE works just like the existing LIKE function
[,match_parameter ]]])
on a regular expression pattern. It returns true or false (a
Parameters: Boolean) indicating whether the pattern matched the data.
• source_string specifies the source data to be scanned. Primarily, it’s used in the WHERE clause (predicate) of

www.pinnaclepublishing.com Oracle Professional June 2004 11


your query. For example, if you need to search rows you prefer. Now, suppose you’d like a list of all phone
containing Canadian postal codes in the employee numbers that don’t have extensions. You could do this by
database that contains employee addresses from all of using REGEXP_INSTR in a simple query as shown here
North America, you could use a regular expression as (again, broken into two lines for formatting purposes):
shown here (note that the regular expression has been
SELECT first_name,email,phone1
wrapped onto two lines due to space constraints; in FROM person_info
practice, you’d combine this into a single line): WHERE REGEXP_INSTR(phone1,
'^[^0-9]*[0-9]{3}[^0-9]*[0-9]{3}[^0-9]*
[0-9]{4}[^0-9]*[0-9]+[^0-9]*$') = 0;
SELECT employee_number
FROM address
WHERE REGEXP_LIKE(postal_code, Notice that [0-9]+ toward the end represents one or
'^[:alpha:][:digit:][:alpha:][ |-]
[:digit:][:alpha:][:digit:]$'); more numeric digits, or an extension. REGEXP_INSTR
can be especially helpful when you want to locate the
In the United States, ZIP Codes are five digits, exact offset of the pattern in the string containing large
optionally followed by a four-digit sub-ZIP Code. Valid RAW data like biological protein sequences.
US ZIP Codes are 94104 and 94587-4443, for example.
In Canada, postal codes are in the form of A9A-9A9. REGEXP_SUBSTR
Examples of valid Canadian postal codes are L4J-6S9 The REGEXP_SUBSTR function searches for the pattern
and M4P-1E2. in the string and returns the matched portion of the
string. In our survey database, if we need to list people
REGEXP_INSTR who frequently take Tylenol or any generic brand of
The REGEXP_INSTR function searches for the pattern acetaminophen, we can use REGEXP_SUBSTR as shown
in the string and returns the beginning position of the in Listing 1.
pattern within the string. REGEXP_INSTR is regular
expression extension to the INSTR function, with one
Listing 1. REGEXP_SUBSTR in action.
exception: REGEXP_INSTR doesn’t work from the end
of the string in the reverse order. SQL> DESC person_info
For example, consider a database that contains Name Null? Type
----------------------------- -------- -------------
survey information. The survey information could be PERSON_ID NOT NULL NUMBER(10)
FIRST_NAME VARCHAR2(50)
entered by people taking surveys on the Web, by data LAST_NAME VARCHAR2(50)
entry operators from surveys received by mail, or from EMAIL1 VARCHAR2(50)
PHONE1 VARCHAR2(50)
some other source. The survey data contains name and DRUGS_FREQUENTLY_USED1 VARCHAR2(100)
phone number, among other information. In order to …
SQL> SELECT drugs_frequently_used1 FROM person_info;
make the survey easy and flexible, users are allowed to
DRUGS_FREQUENLTY_USED1
enter the phone number in practically any format. Here ------------------------------------------------------
are some examples I found in our database: 415 576 3218, Aspirin Anacin Maximum Strength Caplets , Vitamin B
Tylenol Aspirin Anacin Maximum Strength Caplets
(415) 576 3217, 510 9991234, 8881219912 ext. 121, and Aspirin Anacin Maximum Strength Caplets Tiger balm
415-576-3223 x.121. Aspirin Caplets, advil
Aspirin, Folic Acid
Notice that phone numbers are entered in freeform Acetaminophen, Folic Acid

style and may contain an extension at the end. The SELECT first_name
freeform-styled phone numbers, however, still follow ,email1
,regexp_substr(DRUGS_FREQUENLTY_USED1
a pattern: The first three numeric digits in an entered ,'Tylenol|Anacin|acetaminophen'
phone number represent the area code, the next seven ,1,1,'i') acetaminophen_drug
FROM person_info
digits are the phone number, and the remaining digits, WHERE REGEXP_INSTR(DRUGS_FREQUENLTY_USED1
,'Tylenol|Anacin|acetaminophen'
if any, are the extension. Also notice that seven-digit ,1,1,0,'i') > 0;
phone numbers could have a space or a hyphen between
FIRST_NAME EMAIL1 ACETAMINOPHEN_DRUG
the first three and the next four digits. We can use the ---------- ------------------------ ------------------
following regular expression to describe the pattern of Parin pareen@yahoo.com Anacin
Lev lmoltyaner@procase.com Tylenol
phone numbers (broken into two lines for formatting John john@doe.com Anacin
Bob bob@cat.com Acetaminophen
purposes only): …

'^[^0-9]*[0-9]{3}[^0-9]*[0-9]{3}[^0-9]*
[0-9]{4}[^0-9]*[0-9]+[^0-9]*$' Notice that the query uses i as the last parameter
in the REGEXP_SUBSTR function. It specifies case
The ^ and $ characters at each end act as anchors. insensitivity, meaning it will match Tylenol or tylenol.
The [^0-9]* represents any non-numeric character any
number of times. And [0-9] represents any numeric digit. REGEXP_REPLACE
You can use character class [:digit:] instead of [0-9] if The REGEXP_REPLACE function allows you to search

12 Oracle Professional June 2004 www.pinnaclepublishing.com


a pattern in the
string and replace
the matched string
with the supplied
replacement
pattern. REGEXP_
REPLACE
lets you specify
two regular
expression
patterns: one to
search, and one
to replace strings
matched with
the first pattern. Figure 1. Regular expression pattern.
The second
pattern, or the replacement pattern, can refer on an employee using his five-digit employee number.
to the matched string portions using the backslash The search should be sensitive to any typographical errors
(‘\’ - backreference) syntax. This makes REGEXP_ made while searching for the employee number. For
REPLACE very powerful. example, I should be able to search for employee number
I can easily clean up the entered phone numbers in 12345 by entering 12345. However, if I make a mistake
our person_info table by using REGEXP_REPLACE and enter 12354, I should still be able to see employee
as shown in Listing 2 (note that the code has been wrapped 12345 in the retrieved list of employees. Effectively, the
for formatting purposes). I’d like to parse the entered system should search for all possible transposed adjacent
phone number into two separate fields—PHONE_ characters as shown in Figure 2.
NUMBER and EXTENSION—as shown in Table 3. There are several different ways to address this
problem. One way is to write a PL/SQL stored procedure
Listing 2. REGEXP_REPLACE in action.
that will parse the input string and search for all possible
combinations, as shown in Listing 3.
UPDATE person_info
SET phone_number = REGEXP_REPLACE(phone1
,'^[^0-9]*([0-9]{3})[^0-9]*([0-9]{3})[^0-9]* Listing 3. The PL/SQL solution.
([0-9]{4})[^0-9]*([0-9]*)[^0-9]*$'
,'(\1) \2 \3')
,extension = REGEXP_REPLACE(phone1 DECLARE
,'^[^0-9]*([0-9]{3})[^0-9]*([0-9]{3})[^0-9]* type vartab IS TABLE OF CHAR(1)
([0-9]{4})[^0-9]*([0-9]*)[^0-9]*$' INDEX BY BINARY_INTEGER;
,'\4') l_ch vartab;
/ BEGIN
l_ch(1) := substr(p_eno,1,1);
l_ch(2) := substr(p_eno,2,1);
Table 3. The result table. l_ch(3) := substr(p_eno,3,1);
l_ch(4) := substr(p_eno,4,1);
l_ch(5) := substr(p_eno,5,1);
Entered phone number Phone number Extension FOR i IN (
415 576 3218 (415) 576 3218 SELECT first_name,last_name,employee_number
(415) 576 3217 (415) 576 3217 FROM person_info
510 9991234 (510) 999 1234 WHERE employee_number = p_eno OR
employee_number =
8881219912 ext. 121 (888) 121 9912 121 l_ch(2)||l_ch(1)||l_ch(3)||l_ch(4)||l_ch(5) OR
415-576-3223 extension 8012 (415) 576 3223 8012 employee_number =
415-576-3223 x.99 (415) 576 3223 99 l_ch(2)||l_ch(1)||l_ch(4)||l_ch(3)||l_ch(5) OR

Figure 1 shows the different


elements of the pattern used in the
update statement.

A real-life example
One common business problem is to
allow searches to be sensitive to
transposed adjacent characters. For
my example, I’ll use our Web-enabled
HRMS application. I’d like to search Figure 2. The problem definition.

www.pinnaclepublishing.com Oracle Professional June 2004 13


employee_number = SELECT email1 FROM person_info
l_ch(2)||l_ch(1)||l_ch(3)||l_ch(5)||l_ch(4) OR WHERE REGEXP_LIKE(email1,
employee_number = '.*\@.*\.[com|org|edu]');
l_ch(1)||l_ch(3)||l_ch(2)||l_ch(4)||l_ch(5) OR
employee_number = --uses index!!SELECT email1 FROM person_info
l_ch(1)||l_ch(3)||l_ch(2)||l_ch(5)||l_ch(4) OR WHERE REGEXP_LIKE(email1,'.*.org');
employee_number =
l_ch(1)||l_ch(2)||l_ch(4)||l_ch(3)||l_ch(5) OR
employee_number = The behavior of the Oracle optimizer is different
l_ch(1)||l_ch(2)||l_ch(3)||l_ch(5)||l_ch(4)) with REGEXP_LIKE than it is with the other three
LOOP
… regular expression functions. REGEXP_LIKE just
END LOOP; checks whether the regexp pattern exists in the
END;
data or not and returns accordingly. The REGEXP_
The alternative solution is to use regular expressions INSTR, REGEXP_SUBSTR, and REGEXP_REPLACE
in a SQL statement, as shown in Listing 4. This solution functions have to scan the data in its entirety to find
uses the REGEXP_REPLACE function. It preserves each where, in the data, the match occurs. This is why
character using the grouping operators “(” and “)” in the function-based indexes may not work as well with
search pattern and references each character by position these three functions.
using backreferences in the replacement string. It’s easy 2. Due to the sensitivity to locale and charactersets,
to maintain once you understand regular expressions. queries with the same regular expressions may
bring different results in databases with different
locales. For example, default case sensitivity or
Listing 4. The SQL solution. accent sensitivity is determined by the NLS_
SORT parameter.
SELECT first_name,last_name,employee_number
FROM person_info
WHERE employee_number = p_eno SQL> alter session set nls_sort=binary;
OR employee_number = regexp_replace(p_eno, Session altered.
'([0-9])([0-9])([0-9])([0-9])([0-9])','\2\1\3\4\5')
OR employee_number = regexp_replace(p_eno, --returns null with binary
'([0-9])([0-9])([0-9])([0-9])([0-9])','\2\1\4\3\5') SQL> select regexp_substr('abD','[a-z]{3}') from dual;
OR employee_number = regexp_replace(p_eno, R
'([0-9])([0-9])([0-9])([0-9])([0-9])','\2\1\3\5\4') -
OR employee_number = regexp_replace(p_eno,
'([0-9])([0-9])([0-9])([0-9])([0-9])','\1\3\2\4\5') --in west_European character set, lowercase letters
OR employee_number = regexp_replace(p_eno, --are the same as uppercase letters
'([0-9])([0-9])([0-9])([0-9])([0-9])','\1\3\2\5\4')
OR employee_number = regexp_replace(p_eno, SQL> alter session set nls_sort=west_european;
'([0-9])([0-9])([0-9])([0-9])([0-9])','\1\2\4\3\5') Session altered.
OR employee_number = regexp_replace(p_eno,
'([0-9])([0-9])([0-9])([0-9])([0-9])','\1\2\3\5\4') --returns value with west_european
SQL> select regexp_substr('abD','[a-z]{3}') from dual;
Enter value for p_eno: '12354' REGE
----
FIRST_NAME LAST_NAME EMPLOYEE_NUMBER abD
--------------- -------------------- ---------------
Parin Jhaveri 12345
Lev Moltyaner 12354 3. Regular expression functions can be used in DDL and
PL/SQL. All regexp functions behave the same way
Considerations in PL/SQL. For example, you can use following code
Here are some things to consider when using regular in PL/SQL:
expressions:
1. Normally, queries with regular expression functions IF (REGEXP_LIKE(email,'.*\@.*\.[com|org|edu]') THEN

in predicates don’t use regular indexes. However,
you can define a function-based index with the 4 Extra care should be taken while using regular
same regular expression used in the query. For expressions that can match empty strings. For
example, you can create a function-based index on example, if you use the regular expression z* in
the person_info table as shown here: REGEXP_INSTR, it will always return 1. This is
because REGEXP_INSTR starts from the left and
CREATE INDEX pi_rglike ON person_info
REGEXP_LIKE(email,'.*\@.*\.[com|org|edu]'); moves toward the right looking for 0 or more
occurrences of the letter z (0 occurrence means an
Once you have the index in place, most queries using empty string). It finds an empty string right at the
REGEXP_LIKE will use the index pi_rglike. beginning of ‘anything’ and returns 1.

-- All of following queries use index pi_rglike SQL > SELECT REGEXP_INSTR('anything','z*') FROM dual;
SELECT email1 FROM person_info
WHERE REGEXP_LIKE(email1,'.*.org'); REGEXP_INSTR('ANYTHING','z*')
-----------------------------
--uses index!! 1

14 Oracle Professional June 2004 www.pinnaclepublishing.com


The REGEXP_REPLACE function, on the other hand, Conclusion
finds an empty string at the beginning of every Regular expressions are extremely powerful and, in some
character in ‘anything’ and replaces it with ‘ ’ as cases, are the only choice. Oracle Database version 10g
shown here: has enhanced SQL with the power of regular expressions.
Oracle regular expressions are fully compatible with
SQL > SELECT REGEXP_REPLACE('anything','z*',' ') POSIX, are locale-sensitive, and are multilingual, making
FROM dual;
this one of the most powerful regexp engines available. ▲
REGEXP_REPLACE('A
----------------- Parin Jhaveri is a senior technical lead at Pacific Maritime Association,
a n y t h i n g
San Francisco. Parin has more than 11 years of experience working
with Oracle products. He holds a bachelor’s degree in computer science
5. All of the regexp functions support CHAR, and engineering from Gujarat University, India. Parin specializes in
NCHAR, VARCHAR2, NVARCHAR, CLOB, and system design, database administration, and performance tuning.
NCLOB datatypes. pareen@yahoo.com.

Tip: Current_schema and user_tables


Daniel Clamage

Recently, the DBA manager at a former client asked me to help name you’ve switched to. Really, you haven’t truly logged into
him with a SQL problem. They have a third-party vendor tool that schema. As a result, the USER* views still reflect the
called Ascential, which is an ETL tool that uses the USER* data underprivileged schema, not the one in the current context.
dictionary views. They want Ascential to first connect to a Nevertheless, the DBA manager wanted to be able to “fake it”
database account with minimum privileges and then alter its into using the target schema’s USER* views, such that Ascential
session to a different schema, using: would work as desired. This tip describes one solution I came
up with.
ALTER SESSION SET CURRENT_SCHEMA=B First, I create a table in the privileged schema and grant
SELECT on it to the underprivileged account. In this example,
where schema B is a privileged account. The trouble is, setting the privileged account is PPDEV and the underprivileged
the current schema in this fashion only causes the SQL engine account is PP11553 (“PP” indicating user schemas, instead of
to qualify the unqualified table references with the schema application schemas). Continues

Don’t miss another issue! Subscribe now and save!


Subscribe to Oracle Professional today and receive a special one-year introductory rate:
Just $179* for 12 issues (that’s $20 off the regular rate)

NAME ❑ Check enclosed (payable to Pinnacle Publishing)


❑ Purchase order (in U.S. and Canada only); mail or fax copy
COMPANY
❑ Bill me later
❑ Credit card: __ VISA __MasterCard __American Express
ADDRESS
CARD NUMBER EXP. DATE

CITY STATE/PROVINCE ZIP/POSTAL CODE


SIGNATURE (REQUIRED FOR CARD ORDERS)

COUNTRY IF OTHER THAN U.S.


Detach and return to:
Pinnacle Publishing ▲ 316 N. Michigan Ave. ▲ Chicago, IL 60601
E-MAIL Or fax to 312-960-4106

* Outside the U.S. add $30. Orders payable in


406INS
PHONE (IN CASE WE HAVE A QUESTION ABOUT YOUR ORDER) U.S. funds drawn on a U.S. or Canadian bank.

Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106

www.pinnaclepublishing.com Oracle Professional June 2004 15


-- First log in as PPDEV, the privileged account Next, query the user_tables view to see PPDEV’s tables:
SQL> create table ppdev.blick (v varchar2(1));
SQL> grant select on ppdev.blick to pp11553; SQL> select owner, table_name from user_tables;

OWNER TABLE_NAME
Next, I create a view in PPDEV’s schema to look just like ------ ----------
the USER* data dictionary view. This view, against the ALL* PPDEV BLICK

data dictionary view, adds one WHERE clause to test against


the context of the current schema. I then grant SELECT on it I’ll have to do this for every USER* view I want to make
to PP11553: accessible to the underprivileged schema.
Note that you can’t simply grant SELECT on PPDEV’s view of
SQL> create view ppdev.user_tables as user_tables, because the data dictionary view actually belongs to
select * from sys.all_tables SYS. Creating a view on user_tables also doesn’t help; PP11553
where
owner=sys_context('userenv','current_schema'); can’t see any tables it’s not explicitly privileged to see.
grant select on ppdev.user_tables to pp11553; This query helps to show the difference between who I’m
logged in as and what schema I’m currently using:
Note that in the preceding SQL, I have to qualify user_tables
with the sys user; otherwise, I get the following error: -- Log in as PP11553

SQL> select user, sys_context('userenv','current_schema')


SQL> create view user_tables as
current_user from dual;
*
ERROR at line 1:
USER CURRENT_USER
ORA-01031: insufficient privileges
-------- ------------
PP11553 PPDEV
Now the underprivileged user PP11553 can switch its

context to the privileged schema, as illustrated here:
Dan Clamage has been working with Oracle and especially PL/SQL since
-- Log into another session as PP11553
1995. He lives in Pittsburgh, PA, with his wife, Robin, and their two
SQL> alter session set current_schema=ppdev; daughters, Patricia and Daniele. danielj@clamage.com.

June 2004 Downloads


• 406FEUER.ZIP—Source code to accompany Steven • 406MENCHEN.TXT—Source code to accompany Gary
Feuerstein’s article. Menchen’s article.

For access to current and archive content and source code, log in at www.pinnaclepublishing.com.

Editor: Garry Chan (gchan@procaseconsulting.com) Oracle Professional (ISSN 1525-1756)


Contributing Editor: Bryan Boulton is published monthly (12 times per year) by:
CEO & Publisher: Mark Ragan
Pinnacle Publishing
Group Publisher: Michael King A Division of Lawrence Ragan Communications, Inc.
Executive Editor: Farion Grove 316 N. Michigan Ave., Suite 300
Chicago, IL 60601
Questions?
POSTMASTER: Send address changes to Lawrence Ragan Communications, Inc., 316
Customer Service: N. Michigan Ave., Suite 300, Chicago, IL 60601.

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.

16 Oracle Professional June 2004 www.pinnaclepublishing.com

You might also like