Sie sind auf Seite 1von 44

Fast Track PL/SQL

I/O in PL/SQL:
Managing Files
Displaying Output to Screen
Sending Email
Steven Feuerstein
PL/SQL Evangelist, Quest Software
steven.feuerstein@quest.com
PL/SQL Obsession - www.ToadWorld.com/SF

Copyright 2000-2010 Steven Feuerstein - Page 1


How to benefit most from this session
Watch, listen, focus on concepts and principles.
Download and use any of my training materials:

PL/SQL Obsession http://www.ToadWorld.com/SF

Download and use any of my scripts (examples,


performance scripts, reusable code) from the same
location: the demo.zip file.
filename_from_demo_zip.sql

You have my permission to use all these materials to do


internal trainings and build your own applications.
But remember: they are not production ready.
You must test them and modify them to fit your needs.
Copyright 2000-2008 Steven Feuerstein - Page 2
And there's more!

Copyright 2000-2008 Steven Feuerstein - Page 3


I/O in PL/SQL

You don't always read information from and


write data to a relational table.
Sometimes you need to read/write files.
Sometimes you need to display information
to the screen (system output).
Sometimes you need to send emails.
Sometimes....you need to take advantage of
those built-in (supplied) packages.

Copyright 2000-2010 Steven Feuerstein - Page 4


I/O in PL/SQL Relevant Oracle Technologies

UTL_FILE read and write OS files


DBMS_LOB manipulate BFILEs
External tables
DBMS_OUTPUT send text to the screen
UTL_MAIL send emails
Use external procedures (C code) and Java
stored procedures for OS activities.
I will touch on Java extensions to UTL_FILE.

Copyright 2000-2010 Steven Feuerstein - Page 5


>> File IO in PL/SQL

UTL_FILE allows you to read from and write


to operating system files on the database
server.
Prior to Oracle9i Release 2, it was a fairly
primitive utility, but 9.2 offered many
enhancements.
Remove, copy, rename files.
Work with database directories. Hurray!

Copyright 2000-2007 Steven Feuerstein - Page 6


UTL_FILE Logistics

$ORACLE_HOME/Rdbms/Admin

The UTL_FILE package is defined in the


utlfile.sql file of the RDBMS/Admin directory.
Ask your DBA for read-only access.
Includes a long list of file-specific exceptions.
Documents exceptions for each subprogram.
Most of exceptions raised are non-specific and
minimally useful, as in "Invalid file operation."
UTL_FILE is not installed in Oracle XE.
Copyright 2000-2010 Steven Feuerstein - Page 7
Enabling UTL_FILE Access

While UTL_FILE is available by default to all


schemas connected to Oracle, also by
default you have no ability to actually
read/write files.
Oracle is very cautious.
They don't want you to accidentally delete Oracle
database files, among other things!
So a user with DBA authority must authorize
access to specific directories.
Copyright 2000-2010 Steven Feuerstein - Page 8
Authorizing Directory Access UTL_FILE_DIR

In the bad old days of UTL_FILE, the only way


to enable a directory for read/write was the
UTL_FILE_DIR parameter.
And after you change it, you have to "bounce" the
database before UTL_FILE will recognize the
directory.
Very awkward and inflexible. Not well suited for
the 24x7 world of the 21st century.
utl_file_dir = /tmp
utl_file_dir = /accounts/newdev

Copyright 2000-2007 Steven Feuerstein - Page 9


Authorizing Directory Access the modern way

Now you can take advantage of database


directory objects instead of UTL_FILE_DIR.
A directory is a database object, define with a
CREATE statement.
Once created, it is available for use immediately.
Special privilege needed to create directory.
It is generally a DBA-only action.
Directory names are case-sensitive.
GRANT CREATE ANY DIRECTORY TO <schema_name>

Copyright 2000-2007 Steven Feuerstein - Page 10


Working with Database Directories

The directory hides the actual, hard-coded


location.
CREATE OR REPLACE DIRECTORY ERROR_LOG AS '/tmp/apps/log';

Can grant read and/or write privileges to schemas.


GRANT READ ON DIRECTORY error_log TO SCOTT;

Check the ALL_DIRECTORIES view to determine


which directories you can use.
SELECT owner, directory_name, directory_path FROM ALL_DIRECTORIES;

create_directories.sql
Copyright 2000-2007 Steven Feuerstein - Page 11 utlfile_92.sql
Opening a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('TEMP_DIR', 'test.txt', 'W',
max_linesize => 32767);
UTL_FILE.fclose (fid);
END;

Specify file location, name, operation type and (optionally) the


maximum line size.
Types are 'R' for Read, 'W' for Write and 'A' for Append.
Linesize default is 1024, maximum of 32767
The FOPEN function returns a record ("file handle") based on
the UTL_FILE.FILE_TYPE.
Contains three fields (ID, datatype and byte_mode).
Up to 50 files open per session. fopen.sql
Copyright 2000-2007 Steven Feuerstein - Page 12 fopen_write_conflict.sql
Is a File Open?
DECLARE
fid UTL_FILE.file_type;
BEGIN
fid := UTL_FILE.fopen ('TEMP', 'test.txt', 'W', max_linesize => 32767);
IF UTL_FILE.is_open (fid)
THEN
DBMS_OUTPUT.put_line ('File is open');
ELSE
DBMS_OUTPUT.put_line ('File is closed');
END IF;
UTL_FILE.fclose (fid);
END;

Test to see if file is open with the IS_OPEN


function.
In actuality, this function simply returns TRUE if the
file handle's id field is NOT NULL.
Not much of a test...
Copyright 2000-2007 Steven Feuerstein - Page 13
Reading from a File

DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('TEMP', 'test.txt', 'R');
UTL_FILE.GET_LINE (fid, myline);
UTL_FILE.FCLOSE (fid);
END;

Can only read from a file opened with the "R" mode.
Can only read one line at a time.
The NO_DATA_FOUND exception is raised if you read
past the end of the file.
That's more than a little bit silly and a bad idea.

read_from_file.sql
infile.sf
Copyright 2000-2007 Steven Feuerstein - Page 14 eqfiles.sf
A better GET_LINE function

Reading past the end of a file isn't an error.


It's what you do when you read the full
contents of a file.
But UTL_FILE's architecture requires that
your block terminates and you trap the
exception.
Leading to scary and hard-to-manage code.
So "hide" GET_LINE inside your own file-
reading program. exec_ddl_from_file.sql
getnext.sp
Copyright 2000-2010 Steven Feuerstein - Page 15 file_to_collection.sql
Writing to a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('TEMP', 'test.txt', 'W');
UTL_FILE.PUT_LINE (fid, 'UTL_FILE');
UTL_FILE.PUT (fid, 'is so much fun');
UTL_FILE.PUTF (fid, ' that I never\nwant to %s', 'stop!');
UTL_FILE.FCLOSE (fid);
END;

PUT puts a line without a newline terminator.


PUT_LINE = PUT plus newline character.
PUTF allows for some formatting and substitution.
\n is new line, %s substitutes strings.

create_file.sp
write_to_file.sql
read_and_write.sql
display_file.sql
Copyright 2000-2007 Steven Feuerstein - Page 16 genaa.sql
Flushing content to file

A call to PUT_LINE does not necessarily


mean the text is in the file.
In fact, it most likely is not.
Writing out to the actual file is performed
asynchronously.
You can specify each new line flushed out in
call to PUT_LINE.
Or call UTL_FILE.FFLUSH to write the
accumulated buffer contents out to the file.
put_line_performance.tst
Copyright 2000-2010 Steven Feuerstein - Page 17
Closing a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('TEMP', 'test.txt', 'R');
UTL_FILE.GET_LINE (fid, myline);
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN UTL_FILE.READ_ERROR THEN UTL_FILE.FCLOSE (fid);
END;

Very important to close files when you are done with


them.
And also close them when an exception is raised.
Otherwise the file is left open and data will not be
flushed out to disk.
Close all open files in session with FCLOSE_ALL.
Copyright 2000-2007 Steven Feuerstein - Page 18
High Level Functionality of UTL_FILE

Copy a file with FCOPY


Delete a file with FREMOVE
Rename and move a file with FRENAME
Find current position in file with FGETPOS
Retrieve characteristics of file with
FGETATTR

Copyright 2000-2010 Steven Feuerstein - Page 19


Copy a File

DECLARE
file_suffix VARCHAR2 (100)
:= TO_CHAR (SYSDATE, 'YYYYMMDDHH24MISS');
BEGIN
UTL_FILE.fcopy (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip',
dest_location => 'ARCHIVE_DIR',
dest_filename => 'archive' || file_suffix || '.zip'
);
END;

Copy all lines in the file, or a specify range of


lines by start and end line numbers.
You don't open/close the file.
fcopy.sql
backup_with_fcopy..sql
Copyright 2000-2007 Steven Feuerstein - Page 20 fileIO92.pkg
Remove a File

BEGIN
UTL_FILE.fremove (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip'
);
EXCEPTION
/* Don't forget to check for errors! */

WHEN UTL_FILE.delete_failed
THEN
... Deal with failure to remove
END;

If no error is raised, then you deleted successfully

fremove.sql
fileIO92.pkg

Copyright 2000-2007 Steven Feuerstein - Page 21


Rename/move a File

DECLARE
file_suffix VARCHAR2 (100) := TO_CHAR (SYSDATE, 'YYYYMMDD');
BEGIN
-- Rename/move the entire file in a single step.
UTL_FILE.frename (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip',
dest_location => 'ARCHIVE_DIR',
dest_filename => 'archive' || file_suffix || '.zip',
overwrite => FALSE
);END;

Use FRENAME to change the name of a file.


If you rename with a new directory, you will
move the file.
frename.sql
fileIO92.pkg
Copyright 2000-2007 Steven Feuerstein - Page 22
Find current position in file
DECLARE
fid UTL_FILE.file_type;
l_line VARCHAR2 (32767);
BEGIN
fid := UTL_FILE.fopen ('TEMP', 'temp.txt', 'R', max_linesize => 32767);

FOR indx IN 1 .. 10
LOOP
UTL_FILE.get_line (fid, l_line);
DBMS_OUTPUT.put_line (UTL_FILE.fgetpos (fid));
END LOOP;
UTL_FILE.fclose (fid);
END;

Use the FGETPOS function to get the current


relative offset position within a file, in bytes.
Returns 0 when you are writing to a file.
fgetpos.sql
Copyright 2000-2010 Steven Feuerstein - Page 23
Obtaining attributes of a file
CREATE OR REPLACE FUNCTION flength (
location_in IN VARCHAR2,
file_in IN VARCHAR2
) How big is a file? What
RETURN PLS_INTEGER
IS is its block size? Does
TYPE fgetattr_t IS RECORD (
fexists BOOLEAN, the file exist?
file_length
block_size
PLS_INTEGER,
PLS_INTEGER All valuable questions.
All answered with a call
);

fgetattr_rec
BEGIN
fgetattr_t;
to UTL_FILE.FGETATTR.
UTL_FILE.fgetattr (
location => location_in,
filename => file_in,
fexists => fgetattr_rec.fexists,
file_length => fgetattr_rec.file_length,
block_size => fgetattr_rec.block_size
); flength.sql
RETURN fgetattr_rec.file_length; fileIO92.pkg
END flength;

Copyright 2000-2007 Steven Feuerstein - Page 24


Extending file operations with Java

There are still lots of restrictions in UTL_FILE.


No higher-level file operations (change privileges,
create directory, files in directory, random access
to contents).
Limitations on files you can access (no mapped
files, no use of environmental variables).
If you hit these limits, you could consider
a switch to Java.
But you need the DBA to grant you the Java
privileges to interact with the OS.
JFile.java
xfile.pkg
Copyright 2000-2010 Steven Feuerstein - Page 25
External Tables query from files!

External tables are defined as schema


objects, but the data is stored in a file.
Query the contents of the file using standard
SQL syntax.
Specify structure of file using SQL Loader
control file syntax.

Copyright 2000-2010 Steven Feuerstein - Page 26


External Tables simple example

File contains department information:


1,Human Resources,Chicago
2,Information Services,New York

My create table statement "points" to the file:


CREATE TABLE departments_ext (
deptno NUMBER(6),
dname VARCHAR2(20),
loc VARCHAR2(25)
)
...
LOCATION ('departments.ctl')
)

external_tables*.*
Copyright 2000-2010 Steven Feuerstein - Page 27
External Tables 11.2 example:
get files in directory

In 11.2, Oracle extended the external table


concept to include a preprocessor option.
You specify an operating system file that will
run when you execute a query against the
external table.
CREATE TABLE files_in_temp
( file_name VARCHAR2(1000) )
ORGANIZATION external
( TYPE oracle_loader DEFAULT DIRECTORY TEMP
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
PREPROCESSOR execute_directory: 'show_files.bat'
FIELDS TERMINATED BY WHITESPACE )
LOCATION ('placeholder.txt')
)
external_tables_files_in_dir.*
Copyright 2000-2010 Steven Feuerstein - Page 28
DBMS_LOB and BFILEs

Oracle allows you to also manipulate binary


files or BFILEs through the DBMS_LOB
package.
The intention is to work with really big files, but
they don't have to be.
A BFILE is a pointer to an OS file.
This will not constitute a training on working
with LOBs!

Copyright 2000-2010 Steven Feuerstein - Page 29


DBMS_LOB BFILE functionality

Open and close BFILEs


Compare two BFILES with COMPARE.
Get information about a BFILE
Name and location? exists? length?
Read contents of BFILE
They are read-only structures!
And more...
Read BFILE into LOB
Perform INSTR and SUBSTR operations on a BFILE.
Copyright 2000-2010 Steven Feuerstein - Page 30
Open and Close BFILES

DECLARE
l_bfile BFILE := BFILENAME ('DEMO', 'exec_ddl_from_file2.sql');
BEGIN
DBMS_OUTPUT.put_line ('Exists? ' || DBMS_LOB.fileexists (l_bfile));
DBMS_OUTPUT.put_line ('Open before open? ' || DBMS_LOB.fileisopen (l_bfile));
DBMS_LOB.fileopen (l_bfile);
DBMS_OUTPUT.put_line ('Open after open? ' || DBMS_LOB.fileisopen (l_bfile));
DBMS_LOB.fileclose (l_bfile);
DBMS_OUTPUT.
put_line ('Open after close? ' || DBMS_LOB.fileisopen (l_bfile));
END;

Initialize BFILE pointer with the BFILENAME


function.
Then it's all as expected.

bfile_open_close.sql
Copyright 2000-2010 Steven Feuerstein - Page 31
Compare two BFILES with COMPARE
DECLARE
l_bfile1 BFILE := BFILENAME ('TEMP', 'file1.txt');
l_bfile2 BFILE := BFILENAME ('TEMP', 'file2.txt');
BEGIN
DBMS_LOB.fileopen (l_bfile1); DBMS_LOB.fileopen (l_bfile2);

DBMS_OUTPUT.put_line (
'Equal? ' || DBMS_LOB.compare (file_1 => l_bfile1, file_2 => l_bfile2
, amount => 33, offset_1 => 1, offset_2 => 1
)
);

DBMS_LOB.fileclose (l_bfile1); DBMS_LOB.fileclose (l_bfile2);


END;

Use the COMPARE program to find out if two


BFILEs are the same.
Specify amount of file you want to compare, and
the offset locations in each.
bfile_compare.sql
Copyright 2000-2010 Steven Feuerstein - Page 32
Get information about a BFILE

Get the name of the file: FILEGETNAME


Get length of the file. GETLENGTH
Does the file exist? FILEEXISTS
Is the file open? FILEISOPEN

bfile_demo.sql

Copyright 2000-2010 Steven Feuerstein - Page 33


Read contents of BFILE

DECLARE
l_bfile BFILE := BFILENAME ('DEMO', 'exec_ddl_from_file.sql');
l_contents RAW (32767);
l_amount PLS_INTEGER := 100;
BEGIN
DBMS_LOB.fileopen (l_bfile);
DBMS_LOB.read (l_bfile, l_amount, 1, l_contents);
dbms_output.put_line (l_contents);
DBMS_LOB.fileclose (l_bfile);
END;

You always read into a RAW variable.


After all, these are supposed to be binary files!
The amount is IN OUT it returns the actual
number of bytes read.
bfile_read.sql
Copyright 2000-2010 Steven Feuerstein - Page 34
Writing data to system output

Use DBMS_OUTPUT to display information on


your screen.
Best known built-in package and most abused.
Often used as a cheap and easy debugging or
tracing mechanism.
Historically crippled, today many limitations are
removed.
Unlimited buffer size
Display strings of up to 32767 characters.
SET SERVEROUTPUT ON SIZE UNLIMITED
Copyright 2000-2010 Steven Feuerstein - Page 35
Enable and disable output

Calls to DBMS_OUTPUT.PUT_LINE and


others will do nothing unless output is enabled.
This is often performed by host environments.
SET SERVEROUTPUT ON
You can also enable output with a call to
DBMS_OUTPUT.ENABLE, but it is not
enough.
The host environment must also be ready to
retrieve the contents of the buffer.

Copyright 2000-2010 Steven Feuerstein - Page 36


Put information on screen

All information is placed in buffer, which is


retrieved with a call to GET_LINES.
PUT_LINE puts a line of text and end-of-line
marker.
PUT puts text in buffer without end-of-line
marker.
NEW_LINE adds end-of-line marker.

Copyright 2000-2010 Steven Feuerstein - Page 37


Get information from the buffer

GET_LINE retrieves the next line from the


buffer.
GET_LINES returns all lines in buffer into a
PL/SQL collection.
It is very unlikely that you will ever run into a
need to call the GET* procedures.

dbms_output_demo.sql
Copyright 2000-2010 Steven Feuerstein - Page 38
Summary: DBMS_OUTPUT

Avoid calls to DBMS_OUTPUT.PUT_LINE in


your production code.
Do not use this built-in for tracing or
debugging.
Encapsulate it to make it more useful and
less problematic.
The demo.zip contains numerous
alternatives.

Copyright 2000-2010 Steven Feuerstein - Page 39


Alternatives to DBMS_OUTPUT

The p package
Use "p.l" to say "show me", many overloadings of
datatypes.
The watch package
Write information to screen or database pipe
Quest Error Manager
www.ToadWorld.com/SF
More sophisticated tracing and error
management
p.pks/pkb
Copyright 2000-2010 Steven Feuerstein - Page 40 watch_noplv.pkg
Sending mail from a PL/SQL program

UTL_MAIL makes it much easier to send


email from within PL/SQL by hiding some of
the complexities of UTL_SMTP.
To use UTL_MAIL...
Set the SMTP_OUTPUT_SERVER parameter.
Install the utlmail.sql and prvtmail.plb files under
SYS. That's right - it is not installed by default.
Grant EXECUTE on UTL_MAIL as desired.

Copyright 2000-2007 Steven Feuerstein - Page 41


Send without attachment

The interface to the SEND program mimics


the basic "send email" form of Outlook and
other email programs.
BEGI
UTL_MAIL.send (
sender => 'me@mydomain.com'
,recipients => 'you@yourdomain.com, him@hisdomain.com'
,cc => 'mom@momdomain.com'
,bcc => 'me@mydomain.com'
,subject => 'Cool new API for sending email'
,message =>
'Hi Ya''ll,
Sending email in PL/SQL is *much* easier with UTL_MAIL in 10g.
Give it a try!
Mailfully Yours,
Bill'
);
END;

Copyright 2000-2007 Steven Feuerstein - Page 42


Send with attachment

You can attach RAW or VARCHAR2 content


as an attachment (up to 32K).
BEGIN
UTL_MAIL.send_attachment_raw (
sender => 'me@mydomain.com'
,recipients => 'you@yourdomain.com, him@hisdomain.com'
,cc => 'mom@momdomain.com'
,bcc => 'me@mydomain.com'
,subject => 'Cool new API for sending email'
,message => '...'
,attachment => '...' /* Content of the attachment */
,att_inline => TRUE /* Attachment in-line? */
,att_filename => '...'
/* Name of file to hold the attachment after the mail
is received. */
);
END;

Copyright 2000-2007 Steven Feuerstein - Page 43


The World is Bigger Than Relational Tables

Most of our work in PL/SQL involves reading


and writing the contents of tables.
Sometimes, though, we need to interact with
the world "out there."
In these cases, the built-in packages help by
extending PL/SQL.
UTL_FILE has improved over time, but could use a
lot more work.
DBMS_OUTPUT is unavoidable, but problematic.

Copyright 2000-2010 Steven Feuerstein - Page 44

Das könnte Ihnen auch gefallen