665 lines
26 KiB
MySQL
665 lines
26 KiB
MySQL
|
|
-- +----------------------------------------------------------------------------+
|
||
|
|
-- | Jeffrey M. Hunter |
|
||
|
|
-- | jhunter@idevelopment.info |
|
||
|
|
-- | www.idevelopment.info |
|
||
|
|
-- |----------------------------------------------------------------------------|
|
||
|
|
-- | Copyright (c) 1998-2012 Jeffrey M. Hunter. All rights reserved. |
|
||
|
|
-- |----------------------------------------------------------------------------|
|
||
|
|
-- | DATABASE : Oracle |
|
||
|
|
-- | FILE : example_lob_demonstration.sql |
|
||
|
|
-- | CLASS : Examples |
|
||
|
|
-- | PURPOSE : Example script that demonstrates how to manipulate LOBs using |
|
||
|
|
-- | SQL. |
|
||
|
|
-- | NOTE : As with any code, ensure to test this script in a development |
|
||
|
|
-- | environment before attempting to run it in production. |
|
||
|
|
-- +----------------------------------------------------------------------------+
|
||
|
|
|
||
|
|
Prompt
|
||
|
|
Prompt Dropping all testing LOB tables...
|
||
|
|
Prompt ----------------------------------
|
||
|
|
|
||
|
|
DROP TABLE test_lobs;
|
||
|
|
DROP DIRECTORY tmp_dir;
|
||
|
|
DROP TABLE long_data;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
Prompt Create TEST_LOB table...
|
||
|
|
Prompt ------------------------
|
||
|
|
|
||
|
|
CREATE TABLE test_lobs (
|
||
|
|
c1 NUMBER
|
||
|
|
, c2 CLOB
|
||
|
|
, c3 BFILE
|
||
|
|
, c4 BLOB
|
||
|
|
)
|
||
|
|
LOB (c2) STORE AS (ENABLE STORAGE IN ROW)
|
||
|
|
LOB (c4) STORE AS (DISABLE STORAGE IN ROW)
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt Inserting row with NO LOB locators...
|
||
|
|
Prompt -------------------------------------
|
||
|
|
|
||
|
|
INSERT INTO test_lobs
|
||
|
|
VALUES (1, null, null, null)
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt Inserting row with LOB locators (locators created but point to nothing) ...
|
||
|
|
Prompt ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
INSERT INTO test_lobs
|
||
|
|
VALUES (2, empty_clob(), BFILENAME(null,null), empty_blob())
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +------------------------------------------------------------------------+
|
||
|
|
Prompt | It is possible to insert data directly up to 4K. |
|
||
|
|
Prompt | Even though you are only really accessing the locator, the data is |
|
||
|
|
Prompt | stored as appropriate behind the scenes. When inserting directly into |
|
||
|
|
Prompt | a BLOB either the string must be hex as an implicit HEXTORAW will be |
|
||
|
|
Prompt | done or you can call UTL_RAW.CAST_TO_RAW('the string') to convert it |
|
||
|
|
Prompt | for you. Note '48656C6C6F' = 'Hello'. |
|
||
|
|
Prompt +------------------------------------------------------------------------+
|
||
|
|
Prompt
|
||
|
|
|
||
|
|
INSERT INTO test_lobs
|
||
|
|
VALUES ( 3
|
||
|
|
, 'Some data for record 3.'
|
||
|
|
, BFILENAME(null,null)
|
||
|
|
, '48656C6C6F'||UTL_RAW.CAST_TO_RAW(' there!'))
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
Prompt | Now it is time to select back the data. If you were to try to SELECT |
|
||
|
|
Prompt | all three columns from the test_lobs table, SQL*Plus would give you an |
|
||
|
|
Prompt | error: |
|
||
|
|
Prompt | SQL> SELECT * FROM test_lobs; |
|
||
|
|
Prompt | SQL> Column or attribute type can not be displayed by SQL*Plus |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | SQL*Plus cannot convert the data behind the locator to hex for the BLOB |
|
||
|
|
Prompt | column (c3) nor can it interpret a locator for a BFILE (even when null). |
|
||
|
|
Prompt | In order for this query to run successfully, you would need to enter: |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | column c2 format a60 wrap |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | and ONLY select columns c1 and c2: |
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
Prompt
|
||
|
|
|
||
|
|
COLUMN c2 FORMAT a60 WRAP
|
||
|
|
|
||
|
|
Prompt Query CLOB records...
|
||
|
|
Prompt ---------------------
|
||
|
|
|
||
|
|
SELECT c1, c2 FROM test_lobs;
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
Prompt | In the above query, we are really fetching only the LOB locator. SQL*Plus |
|
||
|
|
Prompt | will also then fetch the corresponding data. If we use a 3GL or PL/SQL we |
|
||
|
|
Prompt | can insert data from a character string variable but not select it into |
|
||
|
|
Prompt | one. For example: |
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
Prompt
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
c_lob VARCHAR2(10);
|
||
|
|
BEGIN
|
||
|
|
c_lob := 'Record 4.';
|
||
|
|
INSERT INTO test_lobs
|
||
|
|
VALUES (4,c_lob,BFILENAME(null,null), EMPTY_BLOB());
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
c_lob VARCHAR2(10);
|
||
|
|
BEGIN
|
||
|
|
SELECT c2 INTO c_lob
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 4;
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +-------------------------------------------------------------+
|
||
|
|
Prompt | NEW IN 8i |
|
||
|
|
Prompt | ========= |
|
||
|
|
Prompt | From version 8.1 it is now possible to convert data stored |
|
||
|
|
Prompt | in longs and long raws to CLOBs and BLOBs respectively. |
|
||
|
|
Prompt | This is done using the TO_LOB function. |
|
||
|
|
Prompt +-------------------------------------------------------------+
|
||
|
|
|
||
|
|
Prompt
|
||
|
|
Prompt Create LONG_DATA table...
|
||
|
|
Prompt -------------------------
|
||
|
|
|
||
|
|
CREATE TABLE long_data (
|
||
|
|
c1 NUMBER
|
||
|
|
, c2 LONG
|
||
|
|
)
|
||
|
|
/
|
||
|
|
|
||
|
|
INSERT INTO long_data
|
||
|
|
VALUES (1, 'This is some long data to be migrated to a CLOB')
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt TO_LOB may be used in CREATE TABLE AS SELECT or INSERT...SELECT statements
|
||
|
|
Prompt ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
INSERT INTO test_lobs
|
||
|
|
SELECT 5, TO_LOB(c2), null, null
|
||
|
|
FROM long_data
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt Query CLOB records...
|
||
|
|
Prompt ---------------------
|
||
|
|
|
||
|
|
SELECT c1, c2
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 5;
|
||
|
|
|
||
|
|
ROLLBACK
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt Creating text file /tmp/rec2.txt...
|
||
|
|
Prompt -----------------------------------
|
||
|
|
|
||
|
|
!echo "This is some data for record 2's BFILE column. The data is\nstored in a file called \"rec2.txt\". The file is placed in \n/tmp.\nThe file comprises a total of 4 lines of text." > /tmp/rec2.txt
|
||
|
|
|
||
|
|
Prompt Creating text file /tmp/rec3.txt...
|
||
|
|
Prompt -----------------------------------
|
||
|
|
|
||
|
|
!echo "This is some data for record 3's BFILE column. The data is\nstored in a file called \"rec3.txt\". The file is placed in\n/tmp. The file comprises a total of 5 lines of text and\nwill be used to demonstrate the functionality of the \nDBMS_LOB package." > /tmp/rec3.txt
|
||
|
|
|
||
|
|
|
||
|
|
Prompt First create the ALIAS for the directory /tmp
|
||
|
|
Prompt ---------------------------------------------
|
||
|
|
|
||
|
|
CREATE DIRECTORY tmp_dir AS '/tmp'
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +------------------------------------------------------------+
|
||
|
|
Prompt | Now update the records to associate the BFILE column with |
|
||
|
|
Prompt | the two files created above. |
|
||
|
|
Prompt +------------------------------------------------------------+
|
||
|
|
Prompt
|
||
|
|
|
||
|
|
UPDATE test_lobs
|
||
|
|
SET c3 = BFILENAME('TMP_DIR','rec2.txt')
|
||
|
|
WHERE c1 = 2
|
||
|
|
/
|
||
|
|
|
||
|
|
UPDATE test_lobs
|
||
|
|
SET c3 = BFILENAME('TMP_DIR','rec3.txt')
|
||
|
|
WHERE c1 = 3
|
||
|
|
/
|
||
|
|
|
||
|
|
commit;
|
||
|
|
|
||
|
|
Prompt +---------------------------------------------------------------------+
|
||
|
|
Prompt | Note the files associated with these columns are READ-ONLY through |
|
||
|
|
Prompt | Oracle. |
|
||
|
|
Prompt | They must be maintained via the operating system itself. To access |
|
||
|
|
Prompt | the BFILE columns you must use the DBMS_LOB package or OCI. |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | Getting lengths of the LOB data. Notice the zero lengths where |
|
||
|
|
Prompt | "empty" locators were specified. |
|
||
|
|
Prompt +---------------------------------------------------------------------+
|
||
|
|
|
||
|
|
COLUMN len_c2 FORMAT 9999
|
||
|
|
COLUMN len_c3 FORMAT 9999
|
||
|
|
COLUMN len_c4 FORMAT 9999
|
||
|
|
|
||
|
|
SELECT
|
||
|
|
c1
|
||
|
|
, DBMS_LOB.GETLENGTH(c2) len_c2
|
||
|
|
, DBMS_LOB.GETLENGTH(c3) len_c3
|
||
|
|
, DBMS_LOB.GETLENGTH(c4) len_c4
|
||
|
|
FROM test_lobs
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +---------------------------------------------------------------------------------+
|
||
|
|
Prompt | Using SUBSTR/INSTR - both may be used on all 3 types (CLOB, BLOB and --BFILE) |
|
||
|
|
Prompt | however for BFILEs the file must first have been opened - hence the functions |
|
||
|
|
Prompt | may only be used within PL/SQL in this case. |
|
||
|
|
Prompt | For SUBSTR the parameters are LOB, amount, offset - the opposite to the |
|
||
|
|
Prompt | standard substr function; for INSTR they are LOB, string, offset, occurence, |
|
||
|
|
Prompt | the latter 2 defaulting to 1 if omitted. So the following does a substr from |
|
||
|
|
Prompt | offset 3 in the CLOB for 9 characters and returns the first occurence of the |
|
||
|
|
Prompt | binary string representing "ello" in the BLOB. |
|
||
|
|
Prompt +---------------------------------------------------------------------------------+
|
||
|
|
|
||
|
|
COLUMN sub_c2 FORMAT a10
|
||
|
|
COLUMN ins_c4 FORMAT 99
|
||
|
|
|
||
|
|
SELECT
|
||
|
|
c1
|
||
|
|
, DBMS_LOB.SUBSTR(c2,9,3) sub_c2
|
||
|
|
, DBMS_LOB.INSTR(c4,UTL_RAW.CAST_TO_RAW('ello'),1,1) ins_c4
|
||
|
|
FROM test_lobs
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +----------------------------------------------------------------+
|
||
|
|
Prompt | The following PL/SQL block demonstrates some of the DBMS_LOB |
|
||
|
|
Prompt | functionality. Note the use of "set long 1000" to prevent the |
|
||
|
|
Prompt | output data from being truncated. |
|
||
|
|
Prompt +----------------------------------------------------------------+
|
||
|
|
|
||
|
|
SET SERVEROUTPUT ON
|
||
|
|
SET LONG 1000
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
b_lob BLOB;
|
||
|
|
c_lob CLOB;
|
||
|
|
c_lob2 CLOB;
|
||
|
|
bf BFILE;
|
||
|
|
buf varchar2(100) :=
|
||
|
|
'This is some text to put into a CLOB column in the' ||
|
||
|
|
chr(10) ||
|
||
|
|
'database. The data spans 2 lines.';
|
||
|
|
n number;
|
||
|
|
fn varchar2(50); --Filename
|
||
|
|
fd varchar2(50); --Directory alias
|
||
|
|
|
||
|
|
--Procedure to print out the LOB value from c_lob, one line
|
||
|
|
--at a time..
|
||
|
|
|
||
|
|
PROCEDURE print_clob IS
|
||
|
|
|
||
|
|
offset number;
|
||
|
|
len number;
|
||
|
|
o_buf varchar2(200);
|
||
|
|
amount number; --}
|
||
|
|
f_amt number := 0; --}To hold the amount of data
|
||
|
|
f_amt2 number; --}to be read or that has been
|
||
|
|
amt2 number := -1; --}read
|
||
|
|
|
||
|
|
BEGIN
|
||
|
|
len := DBMS_LOB.GETLENGTH(c_lob);
|
||
|
|
offset := 1;
|
||
|
|
WHILE len > 0 loop
|
||
|
|
amount := DBMS_LOB.INSTR(c_lob,chr(10),offset,1);
|
||
|
|
--Amount returned is the count from the start of the file,
|
||
|
|
--not from the offset.
|
||
|
|
IF amount = 0 THEN
|
||
|
|
--No more linefeeds so need to read remaining data.
|
||
|
|
amount := len;
|
||
|
|
amt2 := amount;
|
||
|
|
ELSE
|
||
|
|
f_amt2 := amount; --Store position of next LF
|
||
|
|
amount := amount - f_amt; --Calc position from last LF
|
||
|
|
f_amt := f_amt2; --Store position for next time
|
||
|
|
amt2 := amount - 1; --Read up to but not the LF
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
IF amt2 != 0 THEN
|
||
|
|
--If there is a linefeed as the first character then ignore.
|
||
|
|
DBMS_LOB.READ(c_lob,amt2,offset,o_buf);
|
||
|
|
dbms_output.put_line(o_buf);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
len := len - amount;
|
||
|
|
offset := offset+amount;
|
||
|
|
END LOOP;
|
||
|
|
END;
|
||
|
|
|
||
|
|
BEGIN
|
||
|
|
--For record 1 we did not initialise the locators so do so now.
|
||
|
|
--Note the RETURNING clause will retrieve the new lob locators so
|
||
|
|
--we do not need to perform an extra select. The update also
|
||
|
|
--ensures the corresponding row is locked.
|
||
|
|
|
||
|
|
UPDATE test_lobs SET c2 = EMPTY_CLOB(), c4 = EMPTY_BLOB()
|
||
|
|
WHERE c1 = 1 RETURNING c2, c4 INTO c_lob, b_lob;
|
||
|
|
|
||
|
|
--Also select the CLOB locator for record 2.
|
||
|
|
SELECT c2 INTO c_lob2 FROM test_lobs where c1 = 3;
|
||
|
|
|
||
|
|
--Write the above buffer into the CLOB column. Offset is 1, amount
|
||
|
|
--is the size of the buffer.
|
||
|
|
DBMS_LOB.WRITE(c_lob,length(buf),1,buf);
|
||
|
|
|
||
|
|
--See what we've got - a line at a time.
|
||
|
|
print_clob;
|
||
|
|
|
||
|
|
--Add some more data to the above column and row. First commit what
|
||
|
|
--we have. Note when we commit, under 8.0, our LOB locators we
|
||
|
|
--previously held in c_lob, b_lob and c_lob2 will be lost and so must be
|
||
|
|
--reselected. **NEW 8i**: under 8.1 LOB locators may span transactions
|
||
|
|
--for read purposes, thus we no longer need to reselect c_lob2.
|
||
|
|
commit;
|
||
|
|
|
||
|
|
--We must lock the row we are going to update through DBMS_LOB.
|
||
|
|
SELECT c2 INTO c_lob FROM test_lobs WHERE c1 = 1 FOR UPDATE;
|
||
|
|
|
||
|
|
--**NEW 8i**: no longer need this select:
|
||
|
|
--select c2 into c_lob2 from test_lobs where c1 = 3;
|
||
|
|
--First append a linefeed then some data from another CLOB.
|
||
|
|
--Under 8.0 this was a two step process, first you had to get the
|
||
|
|
--the length of the LOB and secondly write the data using an offset
|
||
|
|
--of the length plus one. **NEW 8i**: with 8.1 you have a WRITEAPPEND
|
||
|
|
--function that does the two steps in a single call.
|
||
|
|
--**NEW 8i**: no longer need to get the length:
|
||
|
|
--n := DBMS_LOB.GETLENGTH(c_lob)+1;
|
||
|
|
--DBMS_LOB.WRITE(c_lob,1,n,chr(10)); -- 1 char from offset n
|
||
|
|
|
||
|
|
DBMS_LOB.WRITEAPPEND(c_lob,1,chr(10)); -- **NEW 8i**
|
||
|
|
DBMS_LOB.APPEND(c_lob,c_lob2);
|
||
|
|
dbms_output.put_line(chr(10));
|
||
|
|
print_clob;
|
||
|
|
|
||
|
|
--Compare c_lob2 with the third line of c_lob - they should be
|
||
|
|
--the same - in which case remove it. Note the TRIM function takes
|
||
|
|
--the size at which you wish the LOB to end up, NOT how much you
|
||
|
|
--want to remove.
|
||
|
|
n := DBMS_LOB.GETLENGTH(c_lob) - DBMS_LOB.GETLENGTH(c_lob2);
|
||
|
|
|
||
|
|
IF DBMS_LOB.COMPARE(c_lob,c_lob2,DBMS_LOB.GETLENGTH(c_lob2),n+1,1) = 0 THEN
|
||
|
|
DBMS_LOB.TRIM(c_lob,n-1);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
dbms_output.put_line(chr(10));
|
||
|
|
print_clob;
|
||
|
|
|
||
|
|
--Remove the data from the column completely, ie use ERASE to
|
||
|
|
--remove all bytes from offset 1. Note unlike TRIM, ERASE does not
|
||
|
|
--cause the length of the LOB to be shortened - all bytes are simply
|
||
|
|
--set to zero. Thus GETLENGTH will return 0 after TRIM'ing all bytes
|
||
|
|
--but the original length after ERASE'ing.
|
||
|
|
n := DBMS_LOB.GETLENGTH(c_lob);
|
||
|
|
DBMS_LOB.ERASE(c_lob,n,1);
|
||
|
|
|
||
|
|
--Add data from c_lob2 plus a trailing linefeed.
|
||
|
|
DBMS_LOB.COPY(c_lob,c_lob2,DBMS_LOB.GETLENGTH(c_lob2),1,1);
|
||
|
|
|
||
|
|
--**NEW 8i**: could simply use WRITEAPPEND here.
|
||
|
|
n := DBMS_LOB.GETLENGTH(c_lob2)+1;
|
||
|
|
DBMS_LOB.WRITE(c_lob,1,n,chr(10)); -- 1 char from offset n
|
||
|
|
|
||
|
|
--Now append the column with data read from one of the BFILE
|
||
|
|
--columns.
|
||
|
|
select c3 into bf from test_lobs where c1 = 3;
|
||
|
|
|
||
|
|
--First get and output the file details.
|
||
|
|
DBMS_LOB.FILEGETNAME(bf,fd,fn);
|
||
|
|
dbms_output.put_line(chr(10));
|
||
|
|
dbms_output.put_line('Appending data from file '||fn||
|
||
|
|
' in directory aliased by '||fd||':');
|
||
|
|
dbms_output.put_line(chr(10));
|
||
|
|
--Open the file to read from it - first checking that it does in
|
||
|
|
--fact still exist in the O/S and that it is not already open.
|
||
|
|
|
||
|
|
IF DBMS_LOB.FILEEXISTS(bf) = 1 and
|
||
|
|
DBMS_LOB.FILEISOPEN(bf) = 0 THEN
|
||
|
|
DBMS_LOB.FILEOPEN(bf);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
DBMS_LOB.LOADFROMFILE(c_lob,bf,DBMS_LOB.GETLENGTH(bf),n+1,1);
|
||
|
|
DBMS_LOB.FILECLOSE(bf); -- could use DBMS_LOB.FILECLOSEALL;
|
||
|
|
print_clob;
|
||
|
|
|
||
|
|
commit;
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
SELECT c1, c2
|
||
|
|
FROM test_lobs
|
||
|
|
/
|
||
|
|
|
||
|
|
|
||
|
|
Prompt +------------------------------------------------------------------------+
|
||
|
|
Prompt | An important thing to note when using LOB locators within DBMS_LOB |
|
||
|
|
Prompt | and PL/SQL is that a given locator always gives a read consistent |
|
||
|
|
Prompt | image from when it was selected. You will see any changes that you |
|
||
|
|
Prompt | make to the LOB using that locator and DBMS_LOB, but not those made, |
|
||
|
|
Prompt | even in the same transaction, through other LOB locators pointing to |
|
||
|
|
Prompt | the same LOB values or made via SQL directly. For example: |
|
||
|
|
Prompt +------------------------------------------------------------------------+
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
c_lob CLOB;
|
||
|
|
BEGIN
|
||
|
|
|
||
|
|
SELECT c2
|
||
|
|
INTO c_lob
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 1;
|
||
|
|
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Before update length of c2 is '||
|
||
|
|
DBMS_LOB.GETLENGTH(c_lob));
|
||
|
|
|
||
|
|
UPDATE TEST_LOBS
|
||
|
|
SET c2 = 'This is a string.' where c1 = 1;
|
||
|
|
|
||
|
|
DBMS_OUTPUT.PUT_LINE('After update length of c2 is '||
|
||
|
|
DBMS_LOB.GETLENGTH(c_lob));
|
||
|
|
|
||
|
|
SELECT c2
|
||
|
|
INTO c_lob
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 1;
|
||
|
|
|
||
|
|
DBMS_OUTPUT.PUT_LINE('After reselecting locator length of c2 is '||
|
||
|
|
DBMS_LOB.GETLENGTH(c_lob));
|
||
|
|
|
||
|
|
ROLLBACK;
|
||
|
|
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
Prompt +--------------------------------------------------------------------------+
|
||
|
|
Prompt | NEW IN 8i |
|
||
|
|
Prompt | ========= |
|
||
|
|
Prompt | The following PL/SQL blocks demonstrate the remaining new DBMS_LOB |
|
||
|
|
Prompt | functionality introduced in version 8.1. |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | Temporary LOBs |
|
||
|
|
Prompt | ============== |
|
||
|
|
Prompt | In version 8.1 it is now possible to create temporary LOBs. These are |
|
||
|
|
Prompt | LOB locators that point to LOB values held in the user's temporary |
|
||
|
|
Prompt | tablespace. Temporary LOBs are automatically initialised upon creation |
|
||
|
|
Prompt | and exist for the duration specified in the create command or until |
|
||
|
|
Prompt | explicitly freed by the user. The duration of a temporary LOB may be |
|
||
|
|
Prompt | be session or call. At the end of the given duration the temporary |
|
||
|
|
Prompt | LOB is automatically deleted. Temporary LOBs can be used in the |
|
||
|
|
Prompt | same way as normal internal LOBs through the DBMS_LOB package (note |
|
||
|
|
Prompt | there is no temporary version of a BFILE), however being only part of |
|
||
|
|
Prompt | the temporary tablespace they are not permanently stored in the database |
|
||
|
|
Prompt | and they cause no rollback or undo information to be generated. |
|
||
|
|
Prompt | Temporary LOBs may be cached though. Because versioning (ie keeping |
|
||
|
|
Prompt | copies of pages prior to updates) is not performed for temporary LOBs, |
|
||
|
|
Prompt | if a temporary LOB locator is copied and then used to update the LOB |
|
||
|
|
Prompt | value, the whole LOB value must be copied in order to maintain a read |
|
||
|
|
Prompt | consistent image via both locators. For this reason it is recommended |
|
||
|
|
Prompt | that whenever LOB locators are passed as IN OUT or OUT parameters to |
|
||
|
|
Prompt | procedures, functions or methods, NOCOPY is specified so they are |
|
||
|
|
Prompt | passed by reference. |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | The following example uses a temporary LOB to reverse one of the LOB |
|
||
|
|
Prompt | values in the table and then inserts the reversed LOB as a new row. |
|
||
|
|
Prompt +--------------------------------------------------------------------------+
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
c_lob CLOB; --permanent LOB locator
|
||
|
|
t_lob CLOB; --temporary LOB locator
|
||
|
|
buf varchar2(32000); --}this example assumes the LOB is
|
||
|
|
buf2 varchar2(32000); --}less than 32K.
|
||
|
|
chunk number;
|
||
|
|
len number;
|
||
|
|
offset number;
|
||
|
|
amount number;
|
||
|
|
|
||
|
|
BEGIN
|
||
|
|
|
||
|
|
SELECT c2 INTO c_lob FROM test_lobs WHERE c1 = 1;
|
||
|
|
|
||
|
|
--Create a temporary LOB. The parameters to CREATETEMPORARY are
|
||
|
|
--locator, use caching or not and duration. Set no caching and a
|
||
|
|
--duration of call since the temporary LOB is not required outside
|
||
|
|
--of this PL/SQL block.
|
||
|
|
DBMS_LOB.CREATETEMPORARY(t_lob,FALSE,DBMS_LOB.CALL); --**NEW 8i**
|
||
|
|
|
||
|
|
--**NEW 8i**: Use GETCHUNKSIZE to get the amount of space used in a LOB
|
||
|
|
--chunk for storing the LOB value. Using this amount for reads and
|
||
|
|
--writes of the LOB will improve performance.
|
||
|
|
chunk := DBMS_LOB.GETCHUNKSIZE(c_lob);
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Chunksize of column c2 is '||chunk);
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Chunksize of temporary LOB is '||
|
||
|
|
DBMS_LOB.GETCHUNKSIZE(t_lob)); --for info only
|
||
|
|
|
||
|
|
len := DBMS_LOB.GETLENGTH(c_lob);
|
||
|
|
offset := 1;
|
||
|
|
buf := null;
|
||
|
|
|
||
|
|
WHILE offset < len loop
|
||
|
|
IF len - (offset-1) > chunk then
|
||
|
|
amount := chunk;
|
||
|
|
ELSE
|
||
|
|
amount := len - (offset-1);
|
||
|
|
END IF;
|
||
|
|
buf2 := null;
|
||
|
|
DBMS_LOB.READ(c_lob,amount,offset,buf2);
|
||
|
|
buf := buf||buf2;
|
||
|
|
offset := offset + amount;
|
||
|
|
END LOOP;
|
||
|
|
|
||
|
|
--Reverse the read data and write it to the temporary LOB.
|
||
|
|
buf2 := null;
|
||
|
|
FOR i IN reverse 1..len LOOP
|
||
|
|
buf2 := buf2||substr(buf,i,1);
|
||
|
|
END LOOP;
|
||
|
|
|
||
|
|
--Write the whole lot in one go. Note, if this was a large
|
||
|
|
--amount of data then ideally it should be written using the
|
||
|
|
--available chunksize of the temporary LOB.
|
||
|
|
DBMS_LOB.WRITEAPPEND(t_lob,len,buf2); --**NEW 8i**
|
||
|
|
|
||
|
|
--Now insert a new row into the table setting the CLOB column to
|
||
|
|
--the value of the temporary LOB. This can be done in one of
|
||
|
|
--two ways:
|
||
|
|
--(i) A new row can be inserted with an empty locator, the locator
|
||
|
|
-- retrieved and the LOB value copied with DBMS_LOB.COPY.
|
||
|
|
--(ii) A new row can be inserted passing the temporary LOB locator
|
||
|
|
-- as a bind variable to the insert.
|
||
|
|
--
|
||
|
|
--Using the second method:
|
||
|
|
INSERT INTO test_lobs VALUES (5,t_lob,null,null) RETURNING c2 INTO c_lob;
|
||
|
|
|
||
|
|
--Free the temporary LOB explicitly.
|
||
|
|
IF DBMS_LOB.ISTEMPORARY(t_lob) = 1 THEN
|
||
|
|
DBMS_LOB.FREETEMPORARY(t_lob);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Length of CLOB inserted into record 5 is '||
|
||
|
|
DBMS_LOB.GETLENGTH(c_lob));
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt Query CLOB records...
|
||
|
|
Prompt ---------------------
|
||
|
|
|
||
|
|
SELECT c1, c2
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 5
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
Prompt | OPEN AND CLOSE OPERATIONS |
|
||
|
|
Prompt | ========================= |
|
||
|
|
Prompt | Under version 8.0 the only concept of opening and closing a LOB applies |
|
||
|
|
Prompt | to BFILEs and the opening and closing of the physical O/S files they |
|
||
|
|
Prompt | represent. **NEW 8i**: With 8.1 it is now possible to open and close |
|
||
|
|
Prompt | any type of LOB. The new calls introduced for this functionality are |
|
||
|
|
Prompt | DBMS_LOB.OPEN, DBMS_LOB.CLOSE and DBMS_LOB.ISOPEN. When the given |
|
||
|
|
Prompt | locator is a BFILE, these three routines behave as DBMS_LOB.FILEOPEN, |
|
||
|
|
Prompt | DBMS_LOB.FILECLOSE and DBMS_LOB.FILEISOPEN. When applied to internal |
|
||
|
|
Prompt | LOBs they have the effect of batching up any writes such that triggers |
|
||
|
|
Prompt | on an extensible index will not fire until the DBMS_LOB.CLOSE is called. |
|
||
|
|
Prompt | When a LOB is opened it is with a mode of either read-only or read/write. |
|
||
|
|
Prompt | Setting this mode to read-only, prevents any writes from being performed |
|
||
|
|
Prompt | on the LOB in the current transaction until the LOB is closed. Note it |
|
||
|
|
Prompt | is an error to attempt to open a BFILE for read/write. The concept of |
|
||
|
|
Prompt | openness itself applies to a LOB rather than a locator, hence a LOB may |
|
||
|
|
Prompt | only be opened once within a transaction and closed only when open. |
|
||
|
|
Prompt | Attempting to do otherwise will result in an error. |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | NOTE: |
|
||
|
|
Prompt | ===== |
|
||
|
|
Prompt | The following code segment will give: |
|
||
|
|
Prompt | |
|
||
|
|
Prompt | ORA-22294: cannot update a LOB opened in read-only mode |
|
||
|
|
Prompt | Closing LOB via locator 2 |
|
||
|
|
Prompt +---------------------------------------------------------------------------+
|
||
|
|
|
||
|
|
DECLARE
|
||
|
|
|
||
|
|
c_lob1 CLOB;
|
||
|
|
c_lob2 CLOB;
|
||
|
|
|
||
|
|
BEGIN
|
||
|
|
-- Select without locking the LOB.
|
||
|
|
SELECT c2 INTO c_lob1 FROM test_lobs WHERE c1 = 2;
|
||
|
|
c_lob2 := c_lob1;
|
||
|
|
|
||
|
|
-- Open the LOB as read-only using locator 1.
|
||
|
|
DBMS_LOB.OPEN(c_lob1,DBMS_LOB.LOB_READONLY); --**NEW 8i**
|
||
|
|
|
||
|
|
--Writes are not permitted. The following gives an error:
|
||
|
|
BEGIN
|
||
|
|
DBMS_LOB.WRITEAPPEND(c_lob1,5,'Hello'); --**NEW 8i**
|
||
|
|
EXCEPTION
|
||
|
|
WHEN others THEN
|
||
|
|
DBMS_OUTPUT.PUT_LINE(sqlerrm);
|
||
|
|
END;
|
||
|
|
|
||
|
|
-- Commit and rollback are allowed because no transaction is started.
|
||
|
|
-- The LOB will still be open afterwards.
|
||
|
|
|
||
|
|
ROLLBACK;
|
||
|
|
|
||
|
|
-- Close - can use either locator.
|
||
|
|
IF DBMS_LOB.ISOPEN(c_lob2) = 1 THEN --**NEW 8i**
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Closing LOB via locator 2');
|
||
|
|
DBMS_LOB.CLOSE(c_lob2); --**NEW 8i**
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
IF DBMS_LOB.ISOPEN(c_lob1) = 1 THEN --**NEW 8i**
|
||
|
|
DBMS_OUTPUT.PUT_LINE('Closing LOB via locator 1');
|
||
|
|
DBMS_LOB.CLOSE(c_lob1); --**NEW 8i**
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
-- To open for read/write the record in the database must be locked.
|
||
|
|
SELECT c2 INTO c_lob1 FROM test_lobs WHERE c1 = 2 FOR UPDATE;
|
||
|
|
|
||
|
|
DBMS_LOB.OPEN(c_lob1,DBMS_LOB.LOB_READWRITE); --**NEW 8i**
|
||
|
|
DBMS_LOB.WRITEAPPEND(c_lob1,5,'Hello'); --**NEW 8i**
|
||
|
|
DBMS_LOB.WRITEAPPEND(c_lob1,7,' there.'); --**NEW 8i**
|
||
|
|
|
||
|
|
-- The LOB MUST be closed before committing or rolling back.
|
||
|
|
DBMS_LOB.CLOSE(c_lob1); --**NEW 8i**
|
||
|
|
|
||
|
|
COMMIT;
|
||
|
|
|
||
|
|
END;
|
||
|
|
/
|
||
|
|
|
||
|
|
Prompt Query CLOB records...
|
||
|
|
Prompt ---------------------
|
||
|
|
|
||
|
|
SELECT c2
|
||
|
|
FROM test_lobs
|
||
|
|
WHERE c1 = 2
|
||
|
|
/
|