DBMS_LOB - Extras Springer

advertisement
DBMS_LOB
DBMS_LOB
DBMS_LOB is a package supplied to manipulate Large OBjects (LOBs) in the database. LOBs are new data types
available with Oracle 8, and upwards. LOBs support the storage, and retrieval of up to 4 GB of arbitrary data in a
single database column. They replace the, now deprecated, data types LONG and LONG RAW. LONG types in
Oracle had many shorting comings, such as:
❑
You could only have one per table
❑
You could not manipulate them in a stored procedure once they grew beyond 32 KB
❑
You could not piece-wise modify them readily
❑
Many database operations, such as INSERT INTO T SELECT LONG_COL FROM T2, were not
supported
❑
You could not reference them in a WHERE clause
❑
You could not replicate them
❑
And so on...
The LOB data type overcomes all of these limitations.
Rather than go over each and every function/procedure of the DBMS_LOB package (there are some 25 of them),
I am going to answer the most common questions that come up regarding using the DBMS_LOB package and
LOBs. Much of it is either self-explanatory, or is well covered in the standard Oracle documentation. For LOBs
there are two main documents you are concerned with:
1073
5254AppAF.pdf 1
2/28/2005 6:49:38 PM
Appendix A
❑
Oracle8i Supplied PL/SQL Packages Reference – An overview of the DBMS_LOB package, and
every procedure within, along with a definition of all of the inputs and outputs. Handy to have
for reference purposes. You should give this a quick read through to get an understanding of
the functions you can perform on LOBs.
❑
Oracle8i Application Developer's Guide – Large Objects (LOBs) – An entire document dedicated to
explaining how to program using LOBs in various languages and environments. A must read
for the developer who will be using LOBs.
Additionally, many of the nuances of working with LOBs is language-specific. How you do something in Java,
will be different in C, will be different in PL/SQL, and so on. To this end, Oracle Corporation has actually
developed an Application Developer's Guide by language, for languages such as PL/SQL, OCI, Pro*C, COBOL,
VB, and Java detailing how LOBs interact with each language. There is also a comprehensive Application
Developer's Guide on LOBs, as mentioned above, that is useful, regardless of the language used. I would urge
anyone who is considering using LOBs in their applications to read this document, as well as the languagespecific guide for their language of choice. These documents answer most of the questions you will ask.
What I will cover here are the answers to the frequently asked questions about LOBs, from, 'How can I show
them on the web?', to, 'How can I convert between BLOBs and CLOBs?' – things that aren't covered so well in
the standard documentation. LOBs are extremely easy to use once you familiarize yourself with the DBMS_LOB
package (see the Oracle 8i Supplied PL/SQL Packages Reference for an overview of this package) and if you haven't
done so already, you should do so now before reading this section as it assumes you are ready to go and do
things with LOBs.
How do I Load LOBs?
There are quite a few methods available for loading LOBs. In Chapter 9 on Data Loading for example, I
demonstrate how the SQLLDR tool may be used to load LOBs into the database. Additionally, the Application
Developer's Guide for each language provided by Oracle demonstrate how to create and retrieve a LOB using a
specific host language (it's a little different in each). In my opinion however, if I had a directory full of files to
load, the use of a BFILE, a DIRECTORY object, and the LOADFROMFILE routine would by far be the way to
go.
In Chapter 9 on Data Loading, we covered the topic of using DBMS_LOB.LOADFROMFILE in depth. I will refer
you to that section for all of the details. Also, the section on Conversions here, contains a full example of loading a
CLOB using LOADFROMFILE.
substr
This is just a quick note on the substr function provided by the DBMS_LOB package. Every other substr
function I have ever seen (including the one provided with SQL and PL/SQL) has the following arguments in
the following order:
substr( the-string, from-character, for-number-of-characters );
So, the substr('hello', 3, 2) would be ll – the third and fourth characters (from character 3, for 2
characters). DBMS_LOB.SUBSTR however, defines them as:
dbms_lob.substr( the-lob, for-number-of-characters, from-character )
1074
5254AppAF.pdf 2
2/28/2005 6:49:39 PM
DBMS_LOB
So that same substr with DBMS_LOB would return ell. A very small simple test confirms this behavior:
tkyte@TKYTE816> create table t ( str varchar2(10), lob clob );
Table created.
tkyte@TKYTE816> insert into t values ( 'hello', 'hello' );
1 row created.
tkyte@TKYTE816> select substr( str, 3, 2 ),
2
dbms_lob.substr( lob, 3, 2) lob
3
from t
4 /
SU LOB
-- -------------------ll ell
I am constantly doing it backwards myself. It is just one of those things we have to remember to watch
out for!
SELECT FOR UPDATE and Java
In order to modify a database-based LOB (not a temporary LOB), the row that contains the LOB in the database
must be locked by our session. This is a common point of confusion to Java/JDBC programmers. Consider the
small Java program below. It simply:
❑
Inserts a record (hence you would assume its locked)
❑
Reads out the LOB locator just created
❑
Attempts to use this LOB locator with DBMS_LOB.WRITEAPPEND
As it turns out – this Java program will always encounter the error:
java Test
java.sql.SQLException: ORA-22920: row containing the LOB value is not locked
ORA-06512: at "SYS.DBMS_LOB", line 715
ORA-06512: at line 1
Apparently, the LOB we inserted is not locked by our session any more. This is an unfortunate side effect of the
default 'transactional' mode of JDBC – by default it does not support transactions! After every statement, it
commits work immediately. In the following application, unless you add conn.setAutoCommit (false);
immediately after the getConnection – it will fail. That one line of code should (in my opinion) be the first
line of code after every connect in a JDBC program!
import
import
import
import
java.sql.*;
java.io.*;
oracle.jdbc.driver.*;
oracle.sql.*;
// You need a table:
// create table demo ( id int primary key, theBlob blob );
// in order for this application to execute.
1075
5254AppAF.pdf 3
2/28/2005 6:49:39 PM
Appendix A
class Test {
public static void main (String args [])
throws SQLException , FileNotFoundException, IOException
{
DriverManager.registerDriver
(new oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection
("jdbc:oracle:thin:@aria:1521:ora8i",
"scott", "tiger");
// If this program is to work, uncomment this next line!
// conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
// Insert an empty BLOB into the table
// create it new for the very first time.
stmt.execute
( "insert into demo (id,theBlob) " +
"values (1,empty_blob())" );
// Now, we will read it back out so we can
// load it.
ResultSet rset = stmt.executeQuery
("SELECT theBlob " +
"FROM demo "+
"where id = 1 ");
if(rset.next())
{
// Get the BLOB to load into.
BLOB l_mapBLOB = ((OracleResultSet)rset).getBLOB(1);
// Here is the data we will load into it.
File binaryFile = new File("/tmp/binary.dat");
FileInputStream instream =
new FileInputStream(binaryFile);
// We will load about 32 KB at a time. That's
// the most dbms_lob can handle (PL/SQL limit).
int chunk = 32000;
byte[] l_buffer = new byte[chunk];
int l_nread = 0;
// We'll use the easy writeappend routine to add
// our chunk of file to the end of the BLOB.
OracleCallableStatement cstmt =
(OracleCallableStatement)conn.prepareCall
( "begin dbms_lob.writeappend( :1, :2, :3 ); end;" );
// Read and write, read and write, until done.
cstmt.registerOutParameter( 1, OracleTypes.BLOB );
while ((l_nread= instream.read(l_buffer)) != -1)
{
cstmt.setBLOB( 1, l_mapBLOB );
cstmt.setInt(
2, l_nread );
cstmt.setBytes( 3, l_buffer );
1076
5254AppAF.pdf 4
2/28/2005 6:49:39 PM
DBMS_LOB
cstmt.executeUpdate();
l_mapBLOB = cstmt.getBLOB(1);
}
// Close up the input file and callable statement.
instream.close();
cstmt.close();
}
// Close out the statements.
rset.close();
stmt.close();
conn.close ();
}
}
This is a general shortcoming of JDBC, and it affects LOB operations in particular. I cannot tell you how many
people are surprised to find that an API would presume to commit for them – something that must be done by
the application itself. Only an ex-ODBC programmer might be expecting that! The same thing will happen in
ODBC in its default mode of auto commit as well.
Conversions
Frequently, people have their data in a BLOB, and need it for some reason to appear as a CLOB. Typically,
someone has loaded a mixture of text and binary data into a BLOB column, and this person would like to parse
the text. Parsing the BLOB is difficult since the database will constantly try to convert the raw BLOB data into
hexadecimal, which is not the desired effect. In other cases, people have data in a LONG or LONG RAW that they
would like to process as if it were a CLOB or BLOB, given the APIs for these types are so superior to anything
available for LONGs and LONG RAWs.
Fortunately, these conversions are easy to solve. We can convert:
❑
BLOB data into VARCHAR2
❑
VARCHAR2 into RAW
❑
LONGs into CLOBs
❑
LONG RAWs into BLOBs
We'll deal first with the BLOB to VARCHAR2, and vice versa, conversion and then look at the LONG to CLOB, or
LONG RAW to BLOB conversion.
From BLOB to VARCHAR2 and Back Again
The UTL_RAW package has two very handy routines in it for us to use with BLOBs. We'll cover this package in
more depth later on in thesection on UTL_RAW. These two routines are:
❑
CAST_TO_VARCHAR2 – Takes a RAW input and just changes the data type from RAW to
VARCHAR2. No conversion of data actually happens, it is all really just a data type change.
❑
CAST_TO_RAW – Take a VARCHAR2 as input and makes it RAW. It doesn't change the data, just
changes the data type again.
1077
5254AppAF.pdf 5
2/28/2005 6:49:39 PM
Appendix A
So, if you know the BLOB you have is actually text information, and in the right characterset, and everything,
these functions are truly useful. Let's say someone used the LOADFROMFILE routine we briefly looked at earlier
to load a series of files into a BLOB column. We would like to have the ability to view them in SQL*PLUS
(masking out any 'bad' characters that would cause SQL*PLUS to behave improperly). We can use UTL_RAW to
do this for us. First, we will load up some files into a DEMO table:
scott@DEV816> create table demo
2 ( id
int primary key,
3
theBlob
blob
4 )
5 /
Table created.
scott@DEV816> create or replace directory my_files as '/export/home/tkyte';
Directory created.
scott@DEV816> create sequence blob_seq;
Sequence created.
scott@DEV816> create or replace
2 procedure load_a_file( p_dir_name in varchar2,
3
p_file_name in varchar2 )
4 as
5
l_blob
blob;
6
l_bfile
bfile;
7 begin
8
-- First we must create a LOB in the database. We
9
-- need an empty CLOB, BLOB, or a LOB created via the
10
-- CREATE TEMPORARY API call to load into.
11
12
insert into demo values ( blob_seq.nextval, empty_blob() )
13
returning theBlob into l_Blob;
14
15
-- Next, we open the BFILE we will load
16
-- from.
17
18
l_bfile := bfilename( p_dir_name, p_file_name );
19
dbms_lob.fileopen( l_bfile );
20
21
22
-- Then, we call LOADFROMFILE, loading the CLOB we
23
-- just created with the entire contents of the BFILE
24
-- we just opened.
25
dbms_lob.loadfromfile( l_blob, l_bfile,
26
dbms_lob.getlength( l_bfile ) );
27
28
-- Close out the BFILE we opened to avoid running
29
-- out of file handles eventually.
30
31
dbms_lob.fileclose( l_bfile );
32 end;
33 /
1078
5254AppAF.pdf 6
2/28/2005 6:49:39 PM
DBMS_LOB
Procedure created.
scott@DEV816> exec load_a_file( 'MY_FILES', 'clean.sql' );
PL/SQL procedure successfully completed.
scott@DEV816> exec load_a_file( 'MY_FILES', 'expdat.dmp' );
PL/SQL procedure successfully completed.
So, now I have two files loaded up. One is the script I am working on right here – clean.sql. The other is
some expdat.dmp (export file) I have. Now I will write a routine that is callable from SQL to allow me to view
any arbitrary 4000 byte slice of a BLOB in SQL*PLUS. We can only view 4,000 bytes, as this is a SQL limitation
on the size of a VARCHAR2 data type. The CLEAN function below works much as SUBSTR would work on a
regular string, but it takes a BLOB as input and optionally FROM_BYTE and FOR_BYTES arguments. These allow
us to pick off an arbitrary substring of the BLOB to display. Note here how we use
UTL_RAW.CAST_TO_VARCHAR2 to convert the RAW into a VARCHAR2. If we did not use this routine, the RAW
bytes would be converted into hexadecimal before being placed into the VARCHAR2 field. By using this routine,
we simply 'change the data type' from RAW to VARCHAR2, and no translation whatsoever takes place:
scott@DEV816> create or replace
2 function clean( p_raw in blob,
3
p_from_byte in number default 1,
4
p_for_bytes in number default 4000 )
5 return varchar2
6 as
7
l_tmp varchar2(8192) default
8
utl_raw.cast_to_varchar2(
9
dbms_lob.substr(p_raw,p_for_bytes,p_from_byte)
10
);
11
l_char
char(1);
12
l_return varchar2(16384);
13
l_whitespace varchar2(25) default
14
chr(13) || chr(10) || chr(9);
15
l_ws_char
varchar2(50) default
16
'rnt';
17
18 begin
19
for i in 1 .. length(l_tmp)
20
loop
21
l_char := substr( l_tmp, i, 1 );
22
23
-- If the character is 'printable' (ASCII non-control)
24
-- then just add it. If it happens to be a \, add another
25
-- \ to it, since we will replace newlines and tabs with
26
-- \n and \t and such, so need to be able to tell the
27
-- difference between a file with \n in it, and a newline.
28
29
if ( ascii(l_char) between 32 and 127 )
30
then
31
l_return := l_return || l_char;
32
if ( l_char = '\' ) then
33
l_return := l_return || '\';
1079
5254AppAF.pdf 7
2/28/2005 6:49:39 PM
Appendix A
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
end if;
-- If the character is a 'whitespace', replace it
-- with a special character like \r, \n, \t
elsif ( instr( l_whitespace, l_char ) > 0 )
then
l_return := l_return ||
'\' ||
substr( l_ws_char, instr(l_whitespace,l_char), 1 );
-- Else for all other non-printable characters
-- just put a '.'.
else
l_return := l_return || '.';
end if;
end loop;
-----
Now, just return the first 4000 bytes as
this is all that the SQL will let us see. We
might have more than 4000 characters since CHR(10) will
become \n (double the bytes) and so, this is necessary.
return substr(l_return,1,4000);
end;
/
Function created.
scott@DEV816> select id,
2
dbms_lob.getlength(theBlob) len,
3
clean(theBlob,30,40) piece,
4
dbms_lob.substr(theBlob,40,30) raw_data
5
from demo;
ID
LEN PIECE
---------- ----- -------------------1 3498 \ndrop sequence
blob_seq;\n\ncreate
table d
2
RAW_DATA
-----------------------------0A64726F702073657175656E636520
626C6F625F7365713B0A0A63726561
7465207461626C652064
2048 TE\nRTABLES\n1024\n0 54450A525441424C45530A31303234
\n28\n4000\n........ 0A300A32380A343030300A0001001F
......
00010001000000000000
As you can see, we can view the textual component of the BLOB in SQL*PLUS as clear text now using CLEAN.
If we just use DBMS_LOB.SUBSTR, which returns a RAW, we get a hexadecimal dump. Looking at the
hexadecimal dump, we can see the first byte of the first BLOB is 0A, which is a CHR(10), which is a newline.
We can see in our text dump of the BLOB, that our CLEAN function converted the 0A into \n (newline). This
just confirms our routine is working as expected. Further, in the second BLOB, we can see many binary zeroes
(hexadecimal 00) in the raw dump of the expdat.dmp data. We can see that we turned them into . in our
CLEAN function, as many of these special characters, if dumped to the terminal directly, would display in a nonsensical fashion.
1080
5254AppAF.pdf 8
2/28/2005 6:49:39 PM
DBMS_LOB
In addition to the CAST_TO_VARCHAR2 function, UTL_RAW contains the CAST_TO_RAW function. As
demonstrated above, you may have plain ASCII text stored in a BLOB. If you want to be able to use STRINGs
to update this data, you would have to know how to encode the string in hexadecimal. For example:
scott@DEV816> update demo
2
set theBlob = 'Hello World'
3
where id = 1
4 /
set theBlob = 'Hello World'
*
ERROR at line 2:
ORA-01465: invalid hex number
does not work. The implicit conversion from VARCHAR2 to RAW assumes the string Hello World is a string of
hexadecimal characters. Oracle would take the first two bytes, convert them from hexadecimal to decimal, and
assign this number as byte 1 of the RAW data, and so on. We could either take the time to figure out what the
hexadecimal representation of Hello World was, or we could simply cast our VARCHAR2 into a RAW type –
just change the data type and don't change the bytes contained therein. For example:
scott@DEV816> update demo
2
set theBlob = utl_raw.cast_to_raw('Hello World')
3
where id = 1
4 /
1 row updated.
scott@DEV816> commit;
Commit complete.
scott@DEV816> select id,
2
dbms_lob.getlength(theBlob) len,
3
clean(theBlob) piece,
4
dbms_lob.substr(theBlob,40,1) raw_data
5
from demo
6
where id =1;
ID
LEN PIECE
RAW_DATA
---------- ----- -------------------- -----------------------------1
11 Hello World
48656C6C6F20576F726C64
Using UTL_RAW.CAST_TO_RAW('Hello World') is typically much easier than converting Hello World
into 48656C6C6F20576F726C64.
Converting From LONG/LONG RAW to a LOB
Converting from a LONG or LONG RAW to a LOB is rather straightforward. The supplied SQL function TO_LOB
does the job for us. TO_LOB is a rather restricted function however, in that:
❑
It can only be used in an INSERT or CREATE TABLE AS SELECT statement.
❑
It can only be used in SQL, not in PL/SQL.
1081
5254AppAF.pdf 9
2/28/2005 6:49:39 PM
Appendix A
The ramification of the first restriction is that you cannot perform a statement such as:
alter table t add column clob_column;
update t set clob_column = to_lob( long_column );
alter table t drop column long_column;
The above will fail with:
ORA-00932: inconsistent datatypes
during the UPDATE. In order to bulk convert existing tables with LONGs/LONG RAWs, you must create a new
table. This is probably for the best in any case, since LONGs and LONG RAWs were stored 'inline', in other words,
with the table data itself. If we simply converted them to LOBs and then removed the LONG column, we would
leave the table in pretty bad shape. There would be lots of allocated, but not used, space in the table now.
Rebuilding these objects is for the best.
The ramification of the second restriction is that you cannot use TO_LOB in a PL/SQL block. In order to use
TO_LOB in PL/SQL we must use dynamic SQL. We'll demonstrate this in a moment.
We will take a look at two ways of using TO_LOB in the following examples. One is in the use of the TO_LOB
function in a CREATE TABLE AS SELECT or INSERT INTO statement. The other is useful when the source data
must remain in a LONG or LONG RAW column for the time being. For example, a legacy application needs it to
be in a LONG. You would like other applications to be able to access it as a LOB, giving PL/SQL the opportunity
to have full access to it via the piece-wise DBMS_LOB functions, such as READ and SUBSTR for example.
We'll start by synthesizing some LONG and LONG RAW data:
ops$tkyte@DEV816> create table long_table
2 ( id
int primary key,
3
data
long
4 )
5 /
Table created.
ops$tkyte@DEV816> create table long_raw_table
2 ( id
int primary key,
3
data
long raw
4 )
5 /
Table created.
ops$tkyte@DEV816> declare
2
l_tmp
long := 'Hello World';
3
l_raw
long raw;
4 begin
5
while( length(l_tmp) < 32000 )
6
loop
7
l_tmp := l_tmp || ' Hello World';
8
end loop;
9
10
insert into long_table
11
( id, data ) values
1082
5254AppAF.pdf 10
2/28/2005 6:49:39 PM
DBMS_LOB
12
( 1, l_tmp );
13
14
l_raw := utl_raw.cast_to_raw( l_tmp );
15
16
insert into long_raw_table
17
( id, data ) values
18
( 1, l_raw );
19
20
dbms_output.put_line( 'created long with length = ' ||
21
length(l_tmp) );
22 end;
23 /
created long with length = 32003
PL/SQL procedure successfully completed.
Performing a Mass One-Time Conversion Illustration
So, we have two tables, each with one row and either a LONG or a LONG RAW column. We can do a conversion
from LONG to CLOB as easily as a CREATE TABLE AS SELECT statement now:
ops$tkyte@DEV816> create table clob_table
2 as
3 select id, to_lob(data) data
4
from long_table;
Table created.
Additionally, we could have created the table at another point in time, and use the INSERT INTO variant to
populate this table:
ops$tkyte@DEV816> insert into clob_table
2 select id, to_lob(data)
3
from long_table;
1 row created.
The following simply shows that the TO_LOB function does not operate in a PL/SQL block, and that this is to be
expected:
ops$tkyte@DEV816> begin
2
insert into clob_table
3
select id, to_lob(data)
4
from long_table;
5 end;
6 /
begin
*
ERROR at line 1:
ORA-06550: line 3, column 16:
PLS-00201: identifier 'TO_LOB' must be declared
ORA-06550: line 2, column 5:
PL/SQL: SQL Statement ignored
1083
5254AppAF.pdf 11
2/28/2005 6:49:40 PM
Appendix A
This is easy to work around using dynamic SQL (you will just have to dynamically execute the INSERT, not
statically as above). Now that we've seen how to convert a LONG or LONG RAW into a CLOB or BLOB, we'll
consider performance of the conversion. Typically, tables with LONGs and LONG RAWs are huge. By definition
they are big tables – we are using them to store very large objects. They are in many cases, many gigabytes in
size. The question is, how can we perform a bulk conversion in a timely fashion? I suggest using the following
features:
❑
Unrecoverable operations such as a direct path INSERT and NOLOGGING LOBs
❑
Parallel DML (parallel INSERTs specifically)
❑
Parallel query
Here is an example using these features. I have a rather large IMAGE table, which contains many hundreds of
uploaded files (uploaded from the Web). The fields in this table are the NAME of the document, the MIME_TYPE
(for example, application/MS-Word), the IMG_SIZE of the document in bytes, and finally the document
itself in a LONG RAW. I would like to convert this table into an equivalent table where the document is stored in a
BLOB column. I might start by creating the new table:
scott@DEV816> CREATE TABLE "SCOTT"."T"
2 ("NAME" VARCHAR2(255),
3
"MIME_TYPE" VARCHAR2(255),
4
"IMG_SIZE" NUMBER,
5
"IMAGE" BLOB)
6 PCTFREE 0 PCTUSED 40
7 INITRANS 1
8 MAXTRANS 255
9 NOLOGGING
10 TABLESPACE "USERS"
11 LOB ("IMAGE") STORE AS
12 (TABLESPACE "USERS"
13
DISABLE STORAGE IN ROW CHUNK 32768
14
PCTVERSION 10
15
NOCACHE
16
NOLOGGING
17 ) ;
Table created.
Notice the TABLE and the LOB are NOLOGGING – this is important. You can alter them instead of creating them
this way. Now, to convert the data from the existing IMAGE table, I would execute:
scott@DEV816> ALTER SESSION ENABLE PARALLEL DML;
Session altered.
scott@DEV816> INSERT /*+ APPEND PARALLEL(t,5) */ INTO t
2 SELECT /*+ PARALLEL(long_raw,5) */
3
name, mime_type, img_size, to_lob(image)
4
FROM long_raw;
1084
5254AppAF.pdf 12
2/28/2005 6:49:40 PM
DBMS_LOB
This performs a direct path, parallel insert into non-logged BLOBs. As a matter of comparison, I ran the INSERT
INTO with and without logging enabled, and this was the result (using a subset of rows to be converted):
scott@DEV816> create table t
2 as
3 select name, mime_type, img_size, to_lob(image) image
4 from image where 1=0;
Table created.
scott@DEV816> set autotrace on
scott@DEV816> insert into t
2 select name, mime_type, img_size, to_lob(image) image
3 from image;
99 rows created.
Execution Plan
---------------------------------------------------------0
INSERT STATEMENT Optimizer=CHOOSE
1
0
TABLE ACCESS (FULL) OF 'IMAGE'
Statistics
---------------------------------------------------------1242 recursive calls
36057 db block gets
12843 consistent gets
7870 physical reads
34393500 redo size
1006 bytes sent via SQL*Net to client
861 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
99 rows processed
Note how that generated 34 MB of redo (if you add up the bytes of the 99 images, then I have 32 MB of data).
Now, using the CREATE for T I have above with the NOLOGGING clauses and just using a direct path insert, I
find:
scott@DEV816> INSERT /*+ APPEND */ INTO t
2 SELECT name, mime_type, img_size, to_lob(image)
3
FROM image;
99 rows created.
Execution Plan
---------------------------------------------------------0
INSERT STATEMENT Optimizer=CHOOSE
1
0
TABLE ACCESS (FULL) OF 'IMAGE'
Statistics
---------------------------------------------------------1242 recursive calls
36474 db block gets
13079 consistent gets
6487 physical reads
1085
5254AppAF.pdf 13
2/28/2005 6:49:40 PM
Appendix A
1355104
1013
871
4
2
0
99
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
I generated about 1 MB of log. This conversion ran dramatically faster, and generated much less redo log. Of
course, as is the case with all unrecoverable operations, you must ensure that a database backup takes place in
the near future to ensure the recoverability of these new objects. Otherwise, you may find yourself reconverting
the converted data in the event of a disk failure!
The above example is not actually executable by itself. I just happened to have an IMAGE table lying
around, which had about 200 MB of data in it. This is used to demonstrate large, one-time conversions, and
the differences that NOLOGGING clauses had on the size of the redo log generated.
Performing an 'on the fly' Conversion
In many cases, you would like to be able to access (read) a LONG or LONG RAW from various environments, but
find that you cannot. For example, when using PL/SQL, if the LONG RAW exceeds 32KB in size, you will find it
to be quite impossible to access it. Other languages and interfaces have issues with LONGs and LONG RAWs as
well. Well, using the TO_LOB function and a temporary table, we can easily convert a LONG or LONG RAW into a
CLOB or BLOB on the fly. This is very handy for example when using OAS4.x or WebDB with its file upload
functionality. These tools will upload documents over the Web into a database table, but unfortunately, the data
type of the column they upload into is a LONG RAW. This makes accessing this column via PL/SQL virtually
impossible. The functions below show how to provide access to this data via a BLOB, a snap.
We will start with a temporary table to hold the converted CLOB/BLOB, and a sequence to identify our row:
ops$tkyte@DEV816> create global temporary table lob_temp
2 ( id
int primary key,
3
c_lob clob,
4
b_lob blob
5 )
6 /
Table created.
ops$tkyte@DEV816> create sequence lob_temp_seq;
Sequence created.
Now we'll create functions TO_BLOB and TO_CLOB. These functions use the following logic to convert a LONG
or LONG RAW on the fly:
❑
The end user of this function will select the row ID from the table with the LONG or LONG RAW,
instead of selecting the LONG or LONG RAW column. They will pass to us the column name of
the LONG column, the table name and the row ID, identifying the row they want.
❑
We will get a sequence number to identify the row we will create in the temporary table.
1086
5254AppAF.pdf 14
2/28/2005 6:49:40 PM
DBMS_LOB
❑
Using dynamic SQL, we will TO_LOB their LONG or LONG RAW column. The use of dynamic
SQL not only makes this routine generic (works for any LONG column in any table), but it also
solves the issue that TO_LOB cannot be invoked in PLSQL directly.
❑
We read the BLOB or CLOB we just created, back out, and return it to the caller.
Here is the code for TO_BLOB and TO_CLOB:
ops$tkyte@DEV816> create or replace
2 function to_blob( p_cname in varchar2,
3
p_tname in varchar2,
4
p_rowid in rowid ) return blob
5 as
6
l_blob blob;
7
l_id
int;
8 begin
9
select lob_temp_seq.nextval into l_id from dual;
10
11
execute immediate
12
'insert into lob_temp (id,b_lob)
13
select :id, to_lob( ' || p_cname || ' )
14
from ' || p_tname ||
15
' where rowid = :rid '
16
using IN l_id, IN p_rowid;
17
18
select b_lob into l_blob from lob_temp where id = l_id ;
19
20
return l_blob;
21 end;
22 /
Function created.
ops$tkyte@DEV816> create or replace
2 function to_clob( p_cname in varchar2,
3
p_tname in varchar2,
4
p_rowid in rowid ) return clob
5 as
6
l_clob clob;
7
l_id
int;
8 begin
9
select lob_temp_seq.nextval into l_id from dual;
10
11
execute immediate
12
'insert into lob_temp (id,c_lob)
13
select :id, to_lob( ' || p_cname || ' )
14
from ' || p_tname ||
15
' where rowid = :rid '
16
using IN l_id, IN p_rowid;
17
18
select c_lob into l_clob from lob_temp where id = l_id ;
19
20
return l_clob;
21 end;
22 /
Function created.
1087
5254AppAF.pdf 15
2/28/2005 6:49:40 PM
Appendix A
Now, to demonstrate their usage, we can use a simple PL/SQL block. We convert the LONG RAW into a BLOB,
and show its length and a little of the data it holds:
ops$tkyte@DEV816> declare
2
l_blob
blob;
3
l_rowid rowid;
4 begin
5
select rowid into l_rowid from long_raw_table;
6
l_blob := to_blob( 'data', 'long_raw_table', l_rowid );
7
dbms_output.put_line( dbms_lob.getlength(l_blob) );
8
dbms_output.put_line(
9
utl_raw.cast_to_varchar2(
10
dbms_lob.substr(l_blob,41,1)
11
)
12
);
13 end;
14 /
32003
Hello World Hello World Hello World Hello
PL/SQL procedure successfully completed.
The code to test TO_CLOB is virtually the same, with the exception that we do not need to utilize the UTL_RAW
functionality:
ops$tkyte@DEV816> declare
2
l_clob
clob;
3
l_rowid rowid;
4 begin
5
select rowid into l_rowid from long_table;
6
l_clob := to_clob( 'data', 'long_table', l_rowid );
7
dbms_output.put_line( dbms_lob.getlength(l_clob) );
8
dbms_output.put_line( dbms_lob.substr(l_clob,41,1) );
9 end;
10 /
32003
Hello World Hello World Hello World Hello
PL/SQL procedure successfully completed.
How to Write a BLOB/CLOB to Disk
This functionality is missing from the DBMS_LOB package. We have methods to load LOBs from files, but not
create a file from a LOB. I mention it here, simply because we have a solution for it in this book. If you refer to
Chapters 18 and 19 on C-Based External Procedures and Java Stored Procedures, I provide both the C and Java code
for an external procedure that will write any BLOB, CLOB, or TEMPORARY LOB to a file on the server's file
system. Both implementations perform the same function – just using different languages. Use whichever is
appropriate with your server (for example, if you do not have the Java option, but you have Pro*C and a C
compiler, then the C-based external procedure would be more appropriate for you).
1088
5254AppAF.pdf 16
2/28/2005 6:49:40 PM
DBMS_LOB
Displaying a LOB on the Web Using PL/SQL
This is a frequently asked question. This example assumes you have one of the following installed and running
on your system:
❑
WebDB's lightweight listener.
❑
OAS 2.x, 3.x or 4.x with the PL/SQL cartridge.
❑
iAS with the mod_plsql module.
Without one of the above three, this example will not work. It relies on the PL/SQL Web Toolkit (commonly
referred to as the HTP functions), and the PL/SQL cartridge or module.
Another assumption we must make is that the character set of the web server (the client of the database) is the
same as the database itself. This is due to the fact that the PL/SQL cartridge or module uses VARCHAR2s as the
data type to return pages from the database. If the client's character set (the web server is the client in this case) is
different from the database's character set, then character set conversion will take place. This conversion will
typically corrupt a BLOB. For example, say you are running the web server on Windows NT. The typical
character set for a client on Windows NT is WE8ISO8859P1 – Western European 8bit. Now, say the database is
running on Solaris. The default and typical character set on that platform is US7ASCII – a 7bit character set. If
you attempt to return a BLOB through a VARCHAR2 interface given these two character sets, you'll find that the
'high bit' is stripped off of the data as it comes out of the database. The data will be changed. Only if both the
client (the web server) and the database server have the same character set will the data be passed 'as is',
unchanged.
So, given that you have the above two assumptions satisfied, we can now see how to use the PL/SQL web
toolkit to display a BLOB on the Web. We'll continue using the example from above (conversions) with the
DEMO table. We'll load one more file:
ops$tkyte@DEV816> exec load_a_file( 'MY_FILES', 'demo.gif' );
PL/SQL procedure successfully completed.
a GIF file. Now, we need a package that can retrieve this GIF, and display it on the Web. It might look like this:
ops$tkyte@DEV816> create or replace package image_get
2 as
3
-- You might have a procedure named
4
-- after each type of document you want
5
-- to get, for example:
6
-- procedure pdf
7
-- procedure doc
8
-- procedure txt
9
-- and so on. Some browsers (MS IE for example)
10
-- seem to prefer file extensions over
11
-- mime types when deciding how to handle
12
-- documents.
13
procedure gif( p_id in demo.id%type );
14 end;
15 /
Package created.
1089
5254AppAF.pdf 17
2/28/2005 6:49:40 PM
Appendix A
ops$tkyte@DEV816> create or replace package body image_get
2 as
3
4 procedure gif( p_id in demo.id%type )
5 is
6
l_lob
blob;
7
l_amt
number default 32000;
8
l_off
number default 1;
9
l_raw
raw(32000);
10 begin
11
12
-- Get the LOB locator for
13
-- our document.
14
select theBlob into l_lob
15
from demo
16
where id = p_id;
17
18
-- Print out the mime header for this
19
-- type of document.
20
owa_util.mime_header( 'image/gif' );
21
22
begin
23
loop
24
dbms_lob.read( l_lob, l_amt, l_off, l_raw );
25
26
-- It is vital to use htp.PRN to avoid
27
-- spurious line feeds getting added to your
28
-- document.
29
htp.prn( utl_raw.cast_to_varchar2( l_raw ) );
30
l_off := l_off+l_amt;
31
l_amt := 32000;
32
end loop;
33
exception
34
when no_data_found then
35
NULL;
36
end;
37 end;
38
39 end;
40 /
Package body created.
So, now if I had a DAD (Database Access Descriptor; part of the normal setup for the PL/SQL cartridge and
module) set up called mydad I can use the URL:
http://myhost:myport/pls/mydata/image_get.gif?p_id=3
1090
5254AppAF.pdf 18
2/28/2005 6:49:40 PM
DBMS_LOB
to retrieve my image. Here we are passing P_ID=3 argument into image_get.gif, asking it to find the LOB
locator we stored in the row with id=3. We could embed this image in a page using the IMG tag as such:
<html>
<head><title>This is my page</title></head>
<body>
Here is my GIF file
<img src=http://myhost:myport/pls/mydata/image_get.gif?p_id=3>
</body>
</html>
Summary
LOBs provide much more functionality than the now deprecated LONG data type. This section answered some of
the questions I receive frequently regarding LOB manipulations. We discussed how to load LOBs into the
database. We saw how to convert from a BLOB to a CLOB, and back again. We investigated how you might
efficiently convert all of your existing legacy LONG and LONG RAW data into CLOB and BLOB data using
unrecoverable and parallel operations. Lastly, we discussed how you might use the PL/SQL Web Toolkit to
retrieve the contents of a CLOB or BLOB, and display this on a web page.
1091
5254AppAF.pdf 19
2/28/2005 6:49:41 PM
Download