Print CLOB content out as is in Oracle SQL script - sql

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

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.

ORA-00900: invalid SQL statement error?What's wrong with my sql?

I try to write a function that split the string,but it shows:ORA-00900: invalid SQL statement error.What's wrong?I think that v_str varchar2(500); or v_strs_last varchar2(4000) := p_value; may be wrong.
CREATE OR REPLACE TYPE strsplit_type IS TABLE OF VARCHAR2 (4000);
create or replace function strsplit(p_value varchar2,
p_split varchar2 := ',')
return strsplit_type
pipelined is
v_idx integer;
v_str varchar2(500);
v_strs_last varchar2(4000) := p_value;
begin
loop
v_idx := instr(v_strs_last, p_split);
exit when v_idx = 0;
v_str := substr(v_strs_last, 1, v_idx - 1);
v_strs_last := substr(v_strs_last, v_idx + 1);
pipe row(v_str);
end loop;
pipe row(v_strs_last);
return;
end strsplit;
My oracle version is Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production.And I run the script in DB SOLO 5.
The error picture is:
Your DB Solo client seems to be interpreting the first semicolon it sees as the end of the statement, which is reasonable for plain SQL (DML or DDL) but not for PL/SQL.
You can see that from the log image you posted; it treats the create function ... v_ids integer part as one statement because that ends with the first semicolon - it compiles but with an error. Then it takes the next chunk, up to the next semicolon, as a separate statement - v_str varchar2(5000) - and it's that which gets the ORA-00900, since it is not valid SQL.
According to the documentation:
The statements must be separated by either semicolon or the 'GO' keyword. You can change the settings to only use a semicolon or 'GO' as statement separator instead of the default setting which accepts either one. When you have more than one statement in the Query Editor, DB Solo will send them to your server one at a time.
So based on that it doesn't seem to understand how to treat PL/SQL differently; but you can change your settings to not treat the semicolons as statement separators - across the board, which means you'd need to add GO after both the create type and create function statements, and any other queries or calls you make. This would be similar to using / everywhere in SQL*Plus or SQL Developer.
It may be easier to use the procedure editor. Presumably after you've created the type and function from the object browser.
Or, of course, use a different client...

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;
/

ORACLE BLOB to FILE

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

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