Você está na página 1de 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 utlfile_92.sql

Copyright 2000-2007 Steven Feuerstein - Page 11

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 eqfiles.sf

Copyright 2000-2007 Steven Feuerstein - Page 14

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 filereading program. exec_ddl_from_file.sql


Copyright 2000-2010 Steven Feuerstein - Page 15

getnext.sp 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.
write_to_file.sql display_file.sql create_file.sp read_and_write.sql genaa.sql

Copyright 2000-2007 Steven Feuerstein - Page 16

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.
Copyright 2000-2007 Steven Feuerstein - Page 20

fcopy.sql backup_with_fcopy..sql 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 ) RETURN PLS_INTEGER IS TYPE fgetattr_t IS RECORD ( fexists BOOLEAN, file_length PLS_INTEGER, block_size PLS_INTEGER );

fgetattr_rec fgetattr_t; BEGIN 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 ); RETURN fgetattr_rec.file_length; END flength;

How big is a file? What is its block size? Does the file exist? All valuable questions. All answered with a call to UTL_FILE.FGETATTR.

flength.sql fileIO92.pkg

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') )

Copyright 2000-2010 Steven Feuerstein - Page 27

external_tables*.*

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') )
Copyright 2000-2010 Steven Feuerstein - Page 28

external_tables_files_in_dir.*

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.
Copyright 2000-2010 Steven Feuerstein - Page 32 bfile_compare.sql

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.


Copyright 2000-2010 Steven Feuerstein - Page 34 bfile_read.sql

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.

Copyright 2000-2010 Steven Feuerstein - Page 38

dbms_output_demo.sql

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
Copyright 2000-2010 Steven Feuerstein - Page 40 p.pks/pkb 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 ,recipients ,cc ,bcc ,subject ,message 'Hi Ya''ll, Sending email in Give it a try! Mailfully Yours, Bill' ); END;
Copyright 2000-2007 Steven Feuerstein - Page 42

( => => => => => =>

'me@mydomain.com' 'you@yourdomain.com, him@hisdomain.com' 'mom@momdomain.com' 'me@mydomain.com' 'Cool new API for sending email'

PL/SQL is *much* easier with UTL_MAIL in 10g.

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

Você também pode gostar