ORACLE BLOB to FILE - sql

I am writing some pl/sql to generate pdf reports that are stored as blobs in an oracle table. I need to loop through this table which has a column for filename and blob and write the blob to the OS as a file with the corresponding filename in the table. I pretty much have completed this code but am running into a snag:
ORA-06550: line 13, column 59:
PL/SQL: ORA-00904: "SIMS_PROD"."PUBLISH_RPT_NEW"."RPT_FILE_NAME": invalid identifier
ORA-06550: line 13, column 12:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
Cause: Usually a PL/SQL compilation error.
Action:
I did read the post on the site: How can I extract files from an Oracle BLOB field? - however - this is only for one file - my table contains hundreds of rows each that has a blob and associated filename - its the looping through this table thats giving me grief.
I need to prefix the schema name, table and column explicitly since I am logged in as a DBA user and not as the owner of the schema itself. Here is my code - what am I missing here or doing wrong. Thanks in advance for any help from the community - its much appreciated.
DECLARE
t_blob BLOB;
t_len NUMBER;
t_file_name VARCHAR2(100);
t_output utl_file.file_type;
t_totalsize NUMBER;
t_position NUMBER := 1;
t_chucklen NUMBER := 4096;
t_chuck RAW(4096);
t_remain NUMBER;
BEGIN
FOR X IN (SELECT SIMS_PROD.publish_rpt_new.RPT_FILE_NAME, SIMS_PROD.publish_rpt_new.RPT_CONTENTS FROM SIMS_PROD.PUBLISH_RPT)
LOOP
-- Get length of blob
SELECT dbms_lob.Getlength (SIMS_PROD.publish_rpt_new.RPT_CONTENTS), SIMS_PROD.publish_rpt_new.RPT_FILE_NAME INTO t_totalsize, t_file_name FROM SIMS_PROD.publish_rpt_new;
t_remain := t_totalsize;
-- The directory TEMPDIR should exist before executing
t_output := utl_file.Fopen ('PDF_REP', t_file_name, 'wb', 32760);
-- Get BLOB
SELECT SIMS_PROD.publish_rpt_new.RPT_CONTENTS INTO t_blob FROM SIMS_PROD.publish_rpt_new;
-- Retrieving BLOB
WHILE t_position < t_totalsize
LOOP
dbms_lob.READ (t_blob, t_chucklen, t_position, t_chuck);
utl_file.Put_raw (t_output, t_chuck);
utl_file.Fflush (t_output);
t_position := t_position + t_chucklen;
t_remain := t_remain - t_chucklen;
IF t_remain < 4096 THEN t_chucklen := t_remain;
END IF;
END LOOP;
END LOOP;
END;

Try replacing the line 13 with this:
FOR X IN (SELECT RPT_FILE_NAME, RPT_CONTENTS FROM SIMS_PROD.PUBLISH_RPT)

It is going to be a too late answer but at least you will know where have you done mistake. The problem is if you use:
FOR X IN (SELECT a, b from table) LOOP
You have to use X.a in next statements of this loop to properly refer to values of selected rows.
So in your code you had to change SIMS_PROD.publish_rpt_new.RPT_CONTENTS to X.SIMS_PROD.publish_rpt_new.RPT_CONTENTS.
I haven't read the rest of your code, so maybe there could be some more mistakes too.

have done a similar thing recently,
ping me for exact additional details
but the gist is this
create/frame excel or pdf as blob and store in to a blob column
use a base 64 converter function to store the same data into clob ( as text )
use sqlplus from windows/linux to spool the text from the clob column
convert the clob to blob with desired filename using os tools (probably ssl/certificate has utility to convert b64 t0 binary back )

Maybe you could consider writing a procedure separetly to save a BLOB as a file (any BLOB from any table) and then just loop through your table passing a BLOB, directory and file name. This way it could be used independently.
Here is the function (it is a function for some other reasons - you can change it to procedure) that does it like described:
Function BLOB2FILE (mBLOB BLOB, mDir VARCHAR2, mFile VARCHAR2) RETURN VarChar2
IS
BEGIN
Declare
utlFile UTL_FILE.FILE_TYPE;
utlBuffer RAW(32767);
utlAmount BINARY_INTEGER := 32767;
utlPos INTEGER := 1;
utlBlobLen INTEGER;
mRet VarChar2(100);
Begin
utlBlobLen := DBMS_LOB.GetLength(mBLOB);
utlFile := UTL_FILE.FOPEN(mDir, mFile,'wb', 32767);
--
WHILE utlPos <= utlBlobLen LOOP
DBMS_LOB.READ(mBLOB, utlAmount, utlPos, utlBuffer);
UTL_FILE.PUT_RAW(utlFile, utlBuffer, TRUE);
utlPos := utlPos + utlAmount;
END LOOP;
--
UTL_FILE.FCLOSE(utlFile);
mRet := 'OK - file created' || mFile;
RETURN mRet;
Exception
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(utlFile) THEN
UTL_FILE.FCLOSE(utlFile);
END IF;
mRet := 'ERR - CLOB_ETL.BLOB2FILE error message ' || Chr(10) || SQLERRM;
RETURN mRet;
End;
END BLOB2FILE;
If you can select your BLOBS from any table just loop and pass it to the function/procedure with the directory and file name...

Related

Error using Oracle Ref Cursor

I'm trying to do something fairly simple, I am trying to automate the removal and back up of tables from my personal table space. I have about 100 tables and want to get rid of all of them (except a table that I'm using to store the table names), but want to keep the data from the tables in case I need them sometime in the future. Below is the code I am trying to use to accomplish this. I am getting an error on the ref cursor, which I'll include below my code. I half expect somebody to tell me I'm an idiot and explain an easier way to do this. If not, please tell me what I'm doing wrong with the way that I am doing it, thanks.
DECLARE
v_folder_name VARCHAR2(100) := 'MY_FOLDER';
TYPE QRY_CURSOR IS REF CURSOR;
v_qry_cursor QRY_CURSOR;
v_file_name VARCHAR2(320);
v_file sys.utl_file.file_type;
v_max_buffer_length CONSTANT BINARY_INTEGER := 32767;
v_qry_str VARCHAR2(4000); --I've tried this with 32767, made no difference
v_drop_string VARCHAR2(4000);
v_dynamic_record VARCHAR2(4000); --tried this with 32767 also
CURSOR GET_TABLE_NAMES IS
SELECT * FROM TEMP_BACKUP_TABLE WHERE TABLE_NAME <> 'TEMP_BACKUP_TABLE';
FUNCTION startFile(file_name VARCHAR2)
--working function, used with many procedures, left out for brevity
END startFile;
FUNCTION closeFile(file_name VARCHAR2)
--working function, used with many procedures, left out for brevity
END closeFile;
BEGIN
INSERT INTO TEMP_BACKUP_TABLE SELECT DISTINCT TABLE_NAME FROM ALL_TAB_COLS WHERE OWNER = 'ME';
COMMIT;
FOR REC IN GET_TABLE_NAMES LOOP
v_file_name := REC.TABLE_NAME;
v_file := startFile(v_file_name);
v_qry_str := 'SELECT * FROM ' || v_file_name;
v_drop_string := 'DROP TABLE ' || v_file_name;
OPEN v_qry_cursor FOR v_qry_str; -- this is the line that returns an error
LOOP
FETCH v_qry_cursor INTO v_dynamic_record;
EXIT WHEN v_qry_cursor%NOTFOUND;
sys.utl_file.put_line(v_file, v_dynamic_record);
END LOOP;
CLOSE v_qry_cursor;
EXECUTE IMMEDIATE v_drop_string;
COMMIT;
v_file := closeFile(v_file_name);
END LOOP;
DELETE FROM TEMP_BACKUP_TABLE;
END;
The error I'm getting is as follows:
Error report:
ORA-00932: inconsistent datatypes: expected - got -
ORA-06512: at line 73
00932. 00000 - "inconsistent datatypes: expected %s got %s"
*cause:
*action:
Thanks for any help.
At a minimum, utl_file.put_line does not take arbitrary record and you can't fetch an arbitrary list of columns into a varchar2.
You could iterate over each column and construct a SQL statement that concatenates the values from each column into a single string. That would include doing things like putting a to_char with an explicit format mask on your date or timestamp columns, adding a delimiter, escaping any delimiters that exist in your data, etc. This is generally a rather tedious and error-prone process. And then you'll need to write a SQL*Loader control file to load the data back in the future.
It sounds like you'd be better off exporting the table using the Oracle export utility. That's a command-line utility (exp or expdp depending on whether you want to use the classic version or the DataPump version) that lets you export the table definition and data to a file that you can load later using the Oracle import utility.

How to update CLOB column from a physical file?

I have a CLOB column in a table which holds very large amount of XML data. I need to update this column's value for one row of table. How can I do this?
I have tried googling, but this visibly simple stuff is not available in a simple language anywhere. Can anybody please suggest?
If I use the normal update query syntax and paste the huge xml content inside the single quotes (the single quote in xml content replaced with 2 single quotes), then the sql developer just disables the execute query button.
update tableName t
set t.clobField = 'How to specify physical file data'
where t.anotherField='value';
You need to create a function that reads the data from the file into a variable of the CLOB data type and returns it as the result.
Try this working example:
create table tclob (id number, filename varchar2 (64), doc clob)
/
insert into tclob values (1, 'test.xml', empty_clob ());
commit;
create or replace function clobLoader (filename varchar2) return clob is
bf bfile := bfilename ('TEMPFILES', filename);
cl clob;
begin
if dbms_lob.fileexists (bf) = 1 then
dbms_lob.createtemporary (cl, true);
dbms_lob.fileopen (bf, dbms_lob.file_readonly);
dbms_lob.loadfromfile (cl, bf, dbms_lob.getlength (bf));
dbms_lob.fileclose (bf);
else cl := empty_clob ();
end if;
return cl;
end;
/
Usage:
update tclob t
set t.doc = clobLoader (t.filename)
where t.id = 1;
1 row updated.
Search the internet for "load clob from file" and you will find a couple of examples. Such as this (http://www.anujparashar.com/blog/loading-text-file-into-clob-field-in-oracle):
DECLARE
v_bfile BFILE;
v_clob CLOB;
BEGIN
v_bfile := BFILENAME (p_file_directory, p_file_name);
IF DBMS_LOB.FILEEXISTS (v_bfile) = 1 THEN
DBMS_LOB.OPEN (v_bfile);
DBMS_LOB.CREATETEMPORARY (v_clob, TRUE, DBMS_LOB.SESSION);
DBMS_LOB.LOADFROMFILE (v_clob, v_bfile, DBMS_LOB.GETLENGTH (v_bfile));
DBMS_LOB.CLOSE (v_bfile);
INSERT INTO tbl_clob (clob_col)
VALUES (v_clob);
END IF;
COMMIT;
END;
/

How do I view a CLOB output parameter in TOAD from an Oracle Stored Procedure?

I have a stored procedure in a package in an Oracle database that has 2 input parameters + 1 output CLOB parameter. How do I view the output in Toad? (Preferably with the user only having execute/select permissions)
Solution:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
DBMS_OUTPUT.PUT_LINE(my_output_parameter);
END;
Don't forget to execute as script, rather than just execute statement, and results appear in the DBMS Output window, not the datagrid.
I guess DBMS_OUTPUT.PUT_LINE has an internal line limit of 255 chars. However it has been removed from 10g Release 2 onwards. You can try inserting the column data in a table and view it later on by querying that table.
Please refer -
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:146412348066
Would you consider printing the CLOB as a result set? You could then use a PIPELINED function (more about them here: PIPELINED functions by Tim Hall) which would return the CLOB line by line, take a look at the example below:
CREATE TABLE my_clob_tab (
id NUMBER,
clob_col CLOB
)
/
INSERT INTO my_clob_tab
VALUES (1,
to_clob('first line' || chr(10) ||
'second line, a longer one' || chr(10) ||
'third'))
/
CREATE OR REPLACE TYPE t_my_line_str AS TABLE OF VARCHAR2(2000)
/
CREATE OR REPLACE FUNCTION print_clob_func(p_id IN NUMBER)
RETURN t_my_line_str PIPELINED
AS
v_buffer VARCHAR2(32767);
v_clob CLOB;
v_len NUMBER;
v_offset NUMBER := 1;
v_line_break_pos NUMBER;
v_amount NUMBER;
BEGIN
SELECT clob_col
INTO v_clob
FROM my_clob_tab
WHERE id = p_id;
IF v_clob IS NOT NULL THEN
v_len := dbms_lob.getlength(v_clob);
WHILE v_offset < v_len
LOOP
v_line_break_pos := instr(v_clob, chr(10), v_offset);
IF v_line_break_pos = 0 THEN
v_amount := v_len - v_offset + 1;
ELSE
v_amount := v_line_break_pos - v_offset;
END IF;
dbms_lob.read(v_clob, v_amount, v_offset, v_buffer);
v_offset := v_offset + v_amount + 1;
PIPE ROW (v_buffer);
END LOOP;
END IF;
END;
/
(the function can be changed so that it takes as a parameter the CLOB you get from your procedure)
The code reads the content of the CLOB line by line (I assumed that the line separator is CHR(10) - if you are on Windows, you can change it to CHR(10) || CHR(13)) and PIPEs each line to the SELECT statement.
The function that reads the clob could also print the output to the standard output via dbms_output.put_line, but it would be trickier, because you'd have to take into account that standard output's maximal line length is limitied to, correct me if I'm wrong, 2000 characters, but it is doable (can't try that solution right now, unfortunately). In the meanwhile, please check above proposal and give me some feedback if that would work for you.
Back to the solution, now we can issue this SELECT statement:
SELECT COLUMN_VALUE AS clob_line_by_line FROM TABLE(print_clob_func(1));
Which will give us the following output:
CLOB_LINE_BY_LINE
-------------------------
first line
second line, a longer one
third
Check it at SQLFiddle: SQLFiddle example
Approach with inserting PL/SQL block and dbms_output:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
declare
vClob CLOB := my_output_parameter;
vPos number;
vLen number;
begin
vLen := DBMS_LOB.GetLength(vClob);
vPos := 1;
while vPos < vLen loop
DBMS_OUTPUT.Put(DBMS_LOB.Substr(vCLOB, 200, vPos));
vPos := vPos + 200;
end loop;
DBMS_OUTPUT.new_line;
end;
END;

Read text file to insert data into Oracle SQL table

I am studying Oracle SQL developer.
What I am doing is reading text file line by line from the folder.
Then Inserting data to the SQL table.
I am able to compile my PROCEDURE, however, it doesn't seem to be inserting the data to file.
Create or Replace PROCEDURE Rfile is
f UTL_FILE.FILE_TYPE;
s VARCHAR2(200);
BEGIN
f := UTL_FILE.FOPEN('C:\Projects\','testdatabinary.txt','R');
IF UTL_FILE.IS_OPEN(f) THEN
LOOP
BEGIN
UTL_FILE.GET_LINE(f,s);
IF s IS NULL THEN
EXIT;
END IF;
INSERT INTO DATAINSERT
(COLUMN1, COLUMN2)
VALUES
(s, 'testdatabinary');
END;
END LOOP;
COMMIT;
END IF;
END;
And I have a table DATAINSERT with two varchar(200) type cols
I am not really sure the reasons that PROCEDURE is not inserting data to table
I just checked error message
Error starting at line 1 in command:
EXEC Rfile
ORA-29280: invalid directory path
ORA-06512: at "SYS.UTL_FILE", line 41
ORA-06512: at "SYS.UTL_FILE", line 478
ORA-06512: at "SYSTEM.RFILE", line 5
Not sure what causing problems. For me its working fine here is my example code
--Reference Site
--https://community.oracle.com/thread/3633577?start=0&tstart=0
set serveroutput on;
CREATE or replace DIRECTORY USER_DIR AS '/home/oracle';
GRANT READ ON DIRECTORY USER_DIR TO PUBLIC;
DECLARE
V1 VARCHAR2(200); --32767
F1 UTL_FILE.FILE_TYPE;
BEGIN
F1 := UTL_FILE.FOPEN('USER_DIR','temp.txt','R');
Loop
BEGIN
UTL_FILE.GET_LINE(F1,V1);
dbms_output.put_line(V1);
EXCEPTION WHEN No_Data_Found THEN EXIT; END;
end loop;
IF UTL_FILE.IS_OPEN(F1) THEN
dbms_output.put_line('File is Open');
end if;
UTL_FILE.FCLOSE(F1);
END;
/
set serveroutput off;
You can't able to enter the path directly to the file open command like this
f := UTL_FILE.FOPEN('C:\Projects\','testdatabinary.txt','R');
Instead of entering path directly, you have to create directory using the below sql query
CREATE or replace DIRECTORY USER_DIR AS 'C:\PROJECTS\';
Then enter the directory name to the file open command. It will be like this
f := UTL_FILE.FOPEN('USER_DIR ','testdatabinary.txt','R');
First procedure trying to load the file from PC into the server whereas in second procedure file is from server to server. UTL_FILE.FOPEN works for server-side PL/SQL. So first procedure wont execute properly... If you want to load a file from PC to server, then you need to user any front end like D#K or VB.
The mistake your making here is using the workstation path, you actually need to be using the directory path.
The quickest way to resolve this, is just to use the WINSCP command.
Use WINSCP to tell you what the directory path is, then simply substitute this new information for the old path you have in your code, and everything should work ok.
This is very conceptual and tricky. which you can find more concepts and details below:
https://ora-data.blogspot.in/2016/11/read-and-writ-text-file-using-UTLFILE.html
Sure will be helpful:
create or replace procedure read_file is
f_line varchar2(2000);
f utl_file.file_type;
f_dir varchar2(250);
fname varchar2(50);
Comma1 varchar(10);
Comma2 varchar(10);
Comma3 varchar(10);
Comma4 varchar(10);
Comma5 varchar(10);
f_empno emp.empno%type;
f_ename emp.ename%type;
f_job emp.job%type;
f_mgr emp.mgr%type;
f_hiredate emp.hiredate%type;
f_sal emp.sal%type;
begin
f_dir := ‘E:\PLSQL’;
fname := ‘input.txt’;
f := utl_file.fopen(‘UTL_FILE_DIR’,fname,’r’); –opening the file using fopen function
loop
begin
utl_file.get_line(f,f_line);
–using a loop continuously get the file’s content using get_line function exception when no_data_found then
exit;
end;
Comma1 := INSTR(f_line, ‘,’ ,1 , 1);
Comma2 := INSTR(f_line, ‘,’ ,1 , 2);
Comma3 := INSTR(f_line, ‘,’ ,1 , 3);
Comma4 := INSTR(f_line, ‘,’ ,1 , 4);
Comma5 := INSTR(f_line, ‘,’ ,1 , 5);
–Each field in the input record is delimited by commas.
–We need to find the location of two commas in the line.
–and use the locations to get the field from the line.
f_empno := to_number(SUBSTR(f_line, 1, Comma1-1));
f_ename := SUBSTR(f_line, Comma1+1, Comma2-Comma1-1);
f_job := SUBSTR(f_line, comma2+1, Comma3-Comma2-1);
f_mgr := to_number(SUBSTR(f_line, comma3+1, Comma4-Comma3-1));
f_hiredate := to_date(SUBSTR(f_line, comma4+1, Comma5-Comma4-1),’dd-mon-yyyy’);
f_sal := to_number(SUBSTR(f_line, comma5+1),’99999′); dbms_output.put_line(f_empno ||’ ‘|| f_ename || ‘ ‘ || f_job || ‘ ‘ || f_mgr ||’ ‘ || f_hiredate||’ ‘|| f_sal);
insert into emp12 VALUES (f_empno,f_ename,f_job,f_mgr,f_hiredate,f_sal);
end loop;
utl_file.fclose(f);
commit;
end;
/
first login with
username:sys as sysdba
password should be the same as used for user 'system'
now enter
SQL> grant execute on UTL_FILE to PUBLIC;
Now login with any user with which you want to create procedure

Print CLOB content out as is in Oracle SQL script

To start with here is the bigger picture of the task I'm trying to do. I need to create a xml file from the results of the particular SQL request and store it in a file on the client computer. For that I have a SQL script that does the DBMS_XMLGen with xslt, which I'm going to run from a command line with sqlplus and pipe the output into a file.
The problem I'm having now is that content of the XML code (stored in CLOB) has to be splitted into smaller chunks for DBMS_OUTPUT.PUT_LINE, and every chunk ends up with a new line character, breaking the structure of the XML code. I wonder if there's a way to print the content of a BLOB as is on the screen?
Here's the example of the SQL script:
SET SERVEROUTPUT ON FORMAT WRAPPED;
set feedback off
DECLARE
v_ctx DBMS_XMLGen.ctxHandle;
v_xml CLOB;
v_xslt CLOB;
l_offset number := 1;
BEGIN
v_ctx := DBMS_XMLGen.newContext('SELECT * FROM TABLE');
-- DBMS_XMLGen.setXSLT(v_ctx, v_xslt); --not relevant here
v_xml := BMS_XMLGen(v_ctx);
DBMS_XMLGen.closeContext(v_ctx);
loop exit when l_offset > dbms_lob.getlength(v_xml);
DBMS_OUTPUT.PUT_LINE (dbms_lob.substr( v_xml, 255, l_offset));
l_offset := l_offset + 255;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(Substr(SQLERRM,1,255));
raise;
END;
/
The output I'm getting is correct apart from the new line character after every 255 symbols. And I can't just remove the end of lines later, I need the XML to be readable
Any ideas?
Cheers,
Leo
You can add a delimiter and print out 254 characters, then in notepad++ (in extended mode ~\r\n) replace this delimiter :
loop exit when l_offset > dbms_lob.getlength(v_xml);
DBMS_OUTPUT.PUT_LINE (dbms_lob.substr( v_xml, 254, l_offset) || '~');
l_offset := l_offset + 255;
end loop;
Why don't you use DBMS_OUTPUT.PUT instead of DBMS_OUTPUT.PUT_LINE? Give it a try. The trick is to add a newline character after printing your content. You can do it by calling DBMS_OUTPUT.NEW_LINE.
One possible approach is to do the following:
Create a database table with a single column and row of type CLOB.
On server, insert the produced XML into that table.
On client run the SQL*PLus script like this:
SET WRAP OFF
SET HEADING OFF
SET ECHO OFF
SPOOL file_name.xml
SELECT your\_clob\_column FROM your\_table;
SPOOL OFF
That will dump your XML into file_name.xml
After that, you will need to truncate you table by issuing:
TRUNCATE TABLE your\_table DROP STORAGE;
otherwise the table won't shrink even if you delete the line with CLOB.
Check APC's CLOB workaround for dbms_output on
https://community.oracle.com/tech/developers/discussion/308557/ora-20000-oru-10027-buffer-overflow-limit-of-1000000-bytes