Using CLOB instead of VARCHAR2 - plsql

We want to create an XML. The current code does it by appending one XML tag at a time to a VARCHAR2 variable.
xmlString VARCHAR2(32767);
....
....
xmlString := xmlString || '<' || elementName || '>' || elementValue || '</' || elementName || '>';
However due to size limitation of 32767 characters on VARCHAR2, we get the following error for a very long XML.
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
The solution we have is to declare a CLOB and write a procedure to keep flushing the VARCHAR2 variable to the CLOB.
v_result clob;
.....
.....
IF xmlString IS NOT NULL THEN
dbms_lob.writeappend( v_result, LENGTH(xmlString), xmlString);
xmlString := NULL;
END IF;
However this would require replacing lots of exiting code lines with calls to the new function. Is there a better way to do this?
Anything similar to Operator Overloading in PLSQL? Can I change the data type of xmlString variable to CLOB and make the || operator to do the work of dbms_lob.writeappend?

Yes, if you change the data type of xmlString to clob, the string concatenation operator will continue to work.
However, building XML this way would be a very poor architecture. That's the sort of architecture that has a high probability of generating invalid XML when, for example, one of the strings happens to have a character that needs to be escaped (or for any of a number of different reasons). Oracle provides a whole host of functions to generate XML (XMLElement, XMLForest, SYS_XMLGen, DBMS_XMLQuery, etc. depending on your use case). It would be far better architecturally to use those built-in functions.

Related

How to convert VARCHAR2 to BLOB inside Oracle 11g PL/SQL after ORA-06502

I have a function, that calculates hash value of big string! First of all I wrote a cursor T1_CUT, which can include variable count of SELECT statements, something like:
SELECT T1.COL1||T1.COL2||...||T1.COLn FROM T1 WHERE id=documentid
SELECT T2.COL1||T2.COL2||...||T2.COLn FROM T2 WHERE id=documentid
...
SELECT Tn.COL1||Tn.COL2||...||Tn.COLn FROM Tn WHERE id=documentid
Each SELECT can contain one or more rows. So, I concat ALL values in rows in each SELECT and ALL SELECTs values in one big string V_RESULT with type VARCHAR2(32767). After that I get hash value (S_HASH_RESULT) of this big string using MD5. It works fine about 8 monthes, but few days ago I've got ORA-06502 (no surprise). It means, that my final big string have more than 32K symbols (we use 1byte character set - CL8MSWIN1251).
Friends, how can I rewrite my function for using BLOB data type to V_RESULT variable, not VARCHAR2(32767).
Below, text of my function:
FUNCTION CALC_HASH (P_PARTAB_ID IN NUMBER, P_DOC_ID IN NUMBER)
RETURN VARCHAR2
IS
S_HASH_RESULT VARCHAR2(1000);
V_RESULT VARCHAR2(32767);
CURSOR T1_CUT IS
...
/*BIG COMPLAIN SELECT*/
...
T1 T1_CUT%ROWTYPE;
TYPE VALUES_T IS TABLE OF VARCHAR2(32767);
L_VALUES VALUES_T;
BEGIN
OPEN T1_CUT;
FETCH T1_CUT INTO T1;
WHILE T1_CUT%FOUND
LOOP
EXECUTE IMMEDIATE
T1.TEXT
BULK COLLECT INTO L_VALUES;
FOR INDX IN 1 .. L_VALUES.COUNT
LOOP
V_RESULT := V_RESULT || '' ||TO_CHAR(L_VALUES (INDX));
END LOOP;
FETCH T1_CUT INTO T1;
END LOOP;
CLOSE T1_CUT;
S_HASH_RESULT := DBMS_OBFUSCATION_TOOLKIT.MD5(input_string=>V_RESULT);
RETURN S_HASH_RESULT;
END CALC_HASH;
Thanks in advance!
there is a function called utl_raw.cast_to_raw(vc) which transforms a varchar2 into a BLOB value.
However, I recommend to use CLOB to store string values. BLOB has no character semantics at all, i.e., NLS_LANG settings are ignored.
EDIT:
if you want to transform VARCHAR2 to CLOB, simply use TO_CLOB

Assign value to a field of rowtype where `field name` is a string

I want to assign a value to a rowtype's field but I don't know how to do it.
Suppose that I have a table X inside my database.
Suppose also that I have the following variables
a ( X%ROWTYPE ), representing a row of the table X
b ( VARCHAR2 ), containing a column name of the table X
c ( VARCHAR2 ), containing what I want to store inside a.b
What I want to do : something like a.b := c.
I've come up with something like this :
EXECUTE IMMEDIATE 'SELECT '|| c || ' INTO a.' || b || ' FROM DUAL';
Apparently, this isn't the right way to go. I get a ORA-0095: missing keyword error.
Can anyone help me with this ?
Here is the complete code :
DECLARE
tRow MyTable%ROWTYPE;
col_name VARCHAR(10) := 'Length';
nValue NUMBER(12,4) := 0.001;
dynamic_request VARCHAR(300);
BEGIN
dynamic_request := 'SELECT '|| nValue || ' INTO tRow.' || col_name || ' FROM DUAL';
EXECUTE IMMEDIATE dynamic_request;
END;
Ok, I solved it !
Short answer : Using a global variable does the trick
Answer Development
Let us consider two facts about dynamic PL/SQL blocks (i.e., PL/SQL blocks written as strings, to be executed trough an EXECUTE IMMEDIATE statement)
[1] There is no such thing as variable scope when you create a dynamic PLSQL block. What I mean by that is, if you do something like this :
CREATE OR REPLACE PROCEDURE DynamicVariableAssignment(
theString IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE 'BEGIN theString := ''test''; END; ';
END;
it will simply not work because the scope of theString is not transfered to the dynamic PL/SQL block. In other words, the dynamic PL/SQL block doesn't "inherit" of any variable, wherever it is executed.
[2] You might say "OK, no panic, I can give input/output arguments to my dynamic PL/SQL block, right ?". Sure you can, but guess what : you can only give SQL types as in/out ! True PL/SQL types on the other hand, such as a myTable%rowtype, are not accepted as an input for a dynamic PL/SQL block. So the answer of hmmftg won't work either :
-- I've reduced the code to the interesting part
dynamic_request := 'BEGIN :t_row.' || col_name || ':= 0.001; END;';
EXECUTE IMMEDIATE dynamic_request USING IN OUT tRow;
-- (where tRow is of type myTable%ROWTYPE)
since tRow is of MyTable%ROWTYPE, it is not a valid SQL type and is therefore not valid as an input to the dynamic PL/SQL block.
The Solution Who would have thought that global variables would come and save the day ? As we said in [1], we have no reference to any variable outside the dynamic PL/SQL block. BUT we can still access global variables defined in package headers !
Let us assume that I have a package kingPackage in which I define the following :
tempVariable myTable%ROWTYPE;
Then I can do this :
FINAL CODE (body only)
-- Copy tRow into temp variable
kingPackage.tempVariable := tRow;
-- We modify the column of the temp variable
vString := 'BEGIN kingPackage.tempVariable.' || col_val || ' := ' || TO_CHAR(vNumber) ||'; END;';
EXECUTE IMMEDIATE vString;
-- The column value has been updated \o/
tRow := kingPackage.tempVariable;
There you go, fellas !
Have a nice day
try this:
CREATE OR REPLACE PROCEDURE ROW_CHANGER(
tRow IN MyTable%ROWTYPE,
col_name IN VARCHAR,
nValue IN NUMBER)
AS
dynamic_request VARCHAR(300);
BEGIN
dynamic_request := 'BEGIN :t_row.'||COL_NAME ||':= :n_value; END;';
EXECUTE IMMEDIATE dynamic_request
USING IN OUT TROW, IN nValue;
END;
this is because in your EXECUTE IMMEDIATE the tRow MyTable%ROWTYPE is not defined,
so we defined it with using statement.

Populate Text Fields on APEX Form using Array

I am using a barcode scanner to scan in barcodes (that consist of the order^location - I use the ^ as a separator) which i then need to separate and put the two values into two text fields. The idea is to scan the barcode into a third text field and then its separated using a process after save is pressed - These are then saved into separate table columns. I have the below which separates the text but I am now trying to get the values held in the array into the text fields on the APEX form.
I have the below code which works in SQL developer with the following lines however when i changed them to try put the value into a text field it fails. I wondered whether i have it correct or maybe incorrect syntax?
dbms_output.put_line(v_array(1)); -- This works in SQL Developer
:P1_ORDER := dbms_output.put_line(v_array(1)); -- Fails in SQL Developer & APEX
I get the following when i attempt to run the below in SQL developer. Please can someone help me pass the values in my array to the text fields on my APEX Form. Thanks.
ORA-06550: line 17, column 13:
PLS-00222: no function with name 'PUT_LINE' exists in this scope
ORA-06550: line 17, column 5:
PL/SQL: Statement ignored
declare
v_array apex_application_global.vc_arr2;
P1_ORDER number;
P1_LOCATION number;
begin
-- Convert delimited string to array
v_array := apex_util.string_to_table(:P1_JOB_NUMBER,'^');
--dbms_output.put_line(v_array(1));
--dbms_output.put_line(v_array(2));
:P1_ORDER := dbms_output.put_line(v_array(1));
:P1_LOCATION := dbms_output.put_line(v_array(2));
end;
See put_line is not a function ,its a procedure in dbms_output package .
You can't assign dbms_output.put_line to any variable .
Try this
declare
v_array apex_application_global.vc_arr2;
P1_ORDER number;
P1_LOCATION number;
begin
-- Convert delimited string to array
v_array := apex_util.string_to_table(:P1_JOB_NUMBER,'^');
--dbms_output.put_line(v_array(1));
--dbms_output.put_line(v_array(2));
P1_ORDER := v_array(1);
P1_LOCATION := v_array(2);
end;

How to include a huge SQL statement into UTL_FILE procedure?

I wrote a procedure using UTL_FILE:
CREATE OR REPLACE PROCEDURE UTL_CREATE_FILE
(
output_file in UTL_FILE.file_type,
log_file in UTL_FILE.file_type,
filename in VARCHAR2 (64),
ddate in VARCHAR2 (19),
sep in NVARCHAR2 (3)
)
IS
BEGIN
sep := Chr(9);
ddate := TO_CHAR (SYSDATE, 'YYYYMMDD');
filename := 'EXT' || ddate || '.dat';
output_file := UTL_FILE.fopen ('C:/home/S/', filename, 'w', 32000);
log_file := UTL_FILE.fopen ('C:/home/S/', 'WEEKLY.log', 'a', 32000);
UTL_FILE.put_line (log_file, TO_CHAR (SYSDATE, 'DD-MM-YYYY HH24:MI:SS') || 'Started with file ' || filename);
select 'HUGE SQL STATEMENT'|| sep || 'Anykey' as OUTLINE from DUAL;
UTL_FILE.put_line (output_file, OUTLINE);
UTL_FILE.fclose (output_file);
UTL_FILE.put_line (log_file, TO_CHAR (SYSDATE, 'DD-MM-YYYY HH24:MI:SS') || 'Finished for file ' || filename);
UTL_FILE.fclose (log_file);
END;
But Toad returns Warning: compiled but with compilation errors.
Could anybody help me?
As a result I would like to receive a EXT.DAT (and logs) in C:/home/S/ directory. Thank you in advance.
TOAD should give you the compilation errors - they are probably on a separate tab (it's been a while since I used that particular IDE).
However, it easy to spot one bloomer: we cannot assign values to parameters defined in IN mode. The purpose of such parameters is that the calling program assigns their values.
However, in this case I think you need to assign ddate and filename, so you should move them out of the procedure's signature and into its declaration section.
sep I would keep as a parameter but give it a default value.
Bear in mind that SQL limits us to 4000 characters in a column . So if 'HUGE SQL STATEMENT' exceeds 3993 characters your code will hurl a runtime error.
If you're making these sorts of errors you're probably not up-to-speed with the intricacies of writing files from PL/SQL. I suggest you read this previous answer of mine and also this one regarding this topic.
You should be able to append this to the end of your script to get the errors (I don't use TOAD, but I'd expect it to support it). It goes after the last end;.
/
show errors;
The compilation errors that stand out to me -
The parameters are being assigned to. This is illegal as "in" parameters. They don't seem to be used for input, so they should probably be removed from the signature. If this is a code snippet and they do provi

Looking for a utility that converts a SQL statement into a dynamic SQL statement

I am looking for a utility that will convert Oracle SQL to a string that can executed dynamically.
Edit:
Yes, consider this simple SQL
SELECT * FROM TABLE
WHERE COLUMN_NAME = 'VALUE'
I have a utility which for T-SQL which converts the above SQL to a synamic SQL as follows:
BEGIN
DECLARE #Exe_String VarChar(2000)
DECLARE #Qt Char(1)
DECLARE #Cr Char(1)
SET #Qt = Char(39)
SET #Cr = Char(10)
SET #Exe_String = 'SELECT * FROM TABLE ' + #Cr
SET #Exe_String = #Exe_String + 'WHERE COLUMN_NAME = ' + #Qt + 'VALUE' + #Qt + '' + #Cr
PRINT #Exe_String
--Execute (#Exe_String)
END
Granted that the code generated good probably be better, yo get the idea, I hope.
I'm looking for the same type of conversion for Oracle SQL.
Here is a tool that I have used a couple of times. You will have to change the output a little to get it to run but it sure beats having to figure out how to escape all the single ticks.
Sql Tuning
After you click on the link it will take you right to the site and a page with sample SQL. Click the "Static SQL to Dynamic SQL" button and you can see how it works. Then input your own sql you want converted and click the button again. Remove the extra tick (') marks in the end and beginning of each line with the exception of the first and last line and pipes (|) don't need to be there either. Hope this helps.
As a raw translation of your T-SQL to PL/SQL
DECLARE
Exe_String VarChar(2000);
Qt CONSTANT Char(1) := CHR(39);
Cr CONSTANT Char(1) := CHR(10);
BEGIN
exe_string := 'SELECT * FROM TABLE '||Cr;
exe_string := exe_string ||
'WHERE COLUMN_NAME = ' || Qt || 'VALUE' ||Qt || '' ||Cr;
dbms_output.put_line(exe_string);
--
EXECUTE IMMEDIATE exe_string;
END;
The obvious difference is that in Oracle the concatenation operator for strings is || rather than +.
Personally, I have a little string manipluation package (let's call it pstring) that I'd use in a case like this - includes functions like enquote(string), standard constants for newline,tab,etc and the ability to do C-style text replacement.
exe_string :=
pstring.substitute_text('SELECT * FROM %s \n WHERE %s = %s',
table_name,column_name,pstring.enquote(value));
Have you considered using bind variables - i.e. :value - rather than dealing with escaping all the internal quotes? It's a good defence against SQL injection.
Obviously there's some difficulty if you have varying numbers of variables (you need to use DBMS_SQL to link them to the statement rather than a simple EXECUTE IMMEDIATE) but for your simple case it would look like this.
PROCEDURE (table_name IN VARCHAR2, column_name IN VARCHAR2)
IS
Exe_String VarChar(2000);
BEGIN
exe_string :=
pstring.substitute_text('SELECT * FROM %s \n WHERE %s = :value',
table_name,column_name);
dbms_output.put_line(exe_string);
--
EXECUTE IMMEDIATE exe_string USING pstring.enquote(value);
END;
Although of course you have to do something with the results of your SQL.
EXECUTE IMMEDIATE exe_string INTO lresult USING pstring.enquote(value);
Which is difficult when the shape of the table may differ - again, you have to look at Type 4 dynamic SQL (DBMS_SQL).

Resources