How to convert BLOB to CLOB? - oracle

I'm using Oracle 11g and I'm trying to find out the length of a text. I normally use select length(myvar) from table, but I can't do that.
The table which I want to query has a BLOB column that saves characters or photos. I want to know the number of characters that my BLOB column has.
I tried to convert my BLOB into a char using UTL_RAW.CAST_TO_VARCHAR2(myblob) from table, but this functions isn't working correctly or maybe I'm making a mistake.
For example:
My BLOB have the word Section, when I see this in the databse in the hexadecimal form I see S.e.c.t.i.o.n.. I don't know why it have those points in between each letter.
Then I used the this query:
select UTL_RAW.CAST_TO_VARCHAR2(myblob)
from table
The result of this query is 'S' so it's not the complete word that my BLOB has, and when I make this query:
select length(UTL_RAW.CAST_TO_VARCHAR2(myblob))
from table
the result is 18, but the word Sections doesn't have 18 characters.
I was trying to convert the BLOB into a VARCHAR, although I think my best choise would be a CLOB because the length of the text that it can save is more than the limit that VARCHAR has. I tried to do that by making this query (I'm not sure if this is correct but is what I found in the internet):
select UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(myblob, 32767, 1))
from table
This query also returns 'S'

For anyone coming to this thread and wants to know how to convert a blob to a clob. Here is an example.
create function clobfromblob(p_blob blob) return clob is
l_clob clob;
l_dest_offsset integer := 1;
l_src_offsset integer := 1;
l_lang_context integer := dbms_lob.default_lang_ctx;
l_warning integer;
begin
if p_blob is null then
return null;
end if;
dbms_lob.createTemporary(lob_loc => l_clob
,cache => false);
dbms_lob.converttoclob(dest_lob => l_clob
,src_blob => p_blob
,amount => dbms_lob.lobmaxsize
,dest_offset => l_dest_offsset
,src_offset => l_src_offsset
,blob_csid => dbms_lob.default_csid
,lang_context => l_lang_context
,warning => l_warning);
return l_clob;
end;

To convert blob to clob, try this:
SELECT TO_CLOB(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(MYBLOB,2000)))
FROM MYTABLE;

SELECT DBMS_LOB.GetLength( myblob ) length_in_bytes
FROM table
will return the length of the BLOB in bytes. It sounds like the character data in your BLOB is probably encoded using the UTF-16 character set so the number of bytes is probably twice the number of characters (depending on the version of Unicode that is being used and the specific data being stored, some characters might require 4 bytes of storage but it is relatively unlikely that you're dealing with any of those characters).
You can use the DBMS_LOB.ConvertToClob procedure to convert a BLOB to a CLOB (though since this is a procedure, you'll need to call it in a PL/SQL block). As part of that conversion, you'll almost certainly need to specify the character set that the data is encoded in-- my assumption is that your application is using the UTF-16 character set but that's just an assumption.

Only converting to CLOB:
select TO_CLOB(UTL_RAW.CAST_TO_VARCHAR2(YOURCLOB)) from DUAL;
Inspired by Craig without limitation on size.

Related

In Oracle APEX I cannot append data more than 32kb in CLOB

I have text stored in a database table, many short rows about 70-90 characters long fields. (of historical reasons). I want to append these fields (rows) into an CLOB in an APEX (CKEditor) and it does exeed 32k in many cases.
I have tried in many ways but it seems to be some limit. My code works fine as long the text is less than 32k! My plan is to save it in a new table and there use a clob instead. I have APEX 5.01.
I get 'ORA-06502: PL/SQL: numeric or value error' when it is over 32k.
declare
l_clob CLOB;
l_seq number;
cursor textrader_cur is
SELECT F1NR,FHTTYP,RADNR,FHTEXT,DATUM,UPPTAGEN,NUSER FROM DATATXT WHERE DATATXT.F1NR = :P10_F1NR ORDER BY F1NR,FHTTYP,RADNR;
TYPE datatext_typ IS TABLE OF DATATXT%ROWTYPE INDEX BY PLS_INTEGER;
l_datatext datatext_typ;
begin
l_clob := empty_clob();
DBMS_LOB.CREATETEMPORARY(l_clob,true);
apex_collection.create_or_truncate_collection(p_collection_name => 'TEXT');
select count(1) into x from DATATXT#HUMANAUTV WHERE DATATXT.F1NR = :P10_F1NR;
if x > 0 then
open textrader_cur;
loop
fetch textrader_cur bulk collect into l_datatext LIMIT 200;
for indx in 1..l_datatext.COUNT loop
y := length(l_datatext(indx).fhtext);
dbms_lob.writeappend (l_clob,y,l_datatext(indx).fhtext);
--l_clob := l_clob || l_datatext(indx).fhtext; -- This causes same error
end loop;
EXIT WHEN l_datatext.COUNT = 0;
end loop;
close textrader_cur;
l_seq := apex_collection.add_member(p_collection_name => 'TEXT',
p_d001 => sysdate,
p_d002 => sysdate,
p_n001 => dbms_lob.getlength(l_clob),
p_clob001 => l_clob);
-- :P10_WP := l_clob;
SELECT clob001 into :P10_WP FROM APEX_COLLECTIONS WHERE SEQ_ID = l_seq AND COLLECTION_NAME='TEXT';
end if;
end;
PL/SQL provides a dbms_lob package to manipulate this data type. The way I had addressed in a different technology (zope/python) similar problem was to create a framework:
To read from db, it returned data as multipe rows
To write to db, it would send data as multipel calls and server eventually combined it.
You can see that here Blob Journey from Database To Browser
The problem is the last line in your code. Session state variables (e.g. P10_WP) are all VARCHAR2, and limited to 32767 characters. You can see that in the APEX functions that call them (example). So you can't assign more than 32k characters to a PL/SQL page item.
But obviously you can put more than 32k characters in an HTML form item! So it's an awkward workaround - you have to get the clob data in and out of the HTML form item without using APEX page items. Typically this is done by writing the clob to a Collection, then using AJAX calls to an Application Process to retrieve it, since JavaScript has no problem with character limits.
It looks like you've gotten partway there on your own, with your TEXT collection, but you'll still have to write your own On-Demand Application Process so you can load the collection into JavaScript, and put it in the HTML form item from there. It'll be easier if you use the built-in apex.ajax.clob functionality with the CLOB_CONTENT collection.
I looked over a few articles about this, and this one is pretty well written and straightforward.
The short version is to change your collection name in your code to CLOB_CONTENT, then put this JavaScript function on your page and call it.
function clob_get(){
var clob_ob = new apex.ajax.clob(
function(){
var rs = p.readyState
if(rs == 1||rs == 2||rs == 3){
$x_Show('AjaxLoading');
}else if(rs == 4){
$s('P10_WP',p.responseText);
$x_Hide('AjaxLoading');
}else{return false;}
}
);
clob_ob._get();
}
PL/SQL in APEX are limited to 32k, pl/sql treats clobs as varchar and thats it. My problem cannot be solved within APEX

How to convert CLOB to UTF8 in an Oracle query?

I've got a query with a CLOB field which I want to return her value in UTF8 format. The next query works fine if the field are varchar, for example, but if it is CLOB doesn't return a correct UTF8 string.
select convert(field, 'AL32UTF8', 'WE8ISO8859P15') from table;
How can I do to return a UTF8 string from a CLOB in a query?
Use DBMS_LOB.CONVERTTOBLOB.
From the oracle documentation:
Oracle discourages the use of the CONVERT function in the current
Oracle Database release. The return value of CONVERT has a character
datatype, so it should be either in the database character set or in
the national character set, depending on the datatype. Any
dest_char_set that is not one of these two character sets is
unsupported. …
If you need a character datatype like CLOB in a character set that differs from those the database is setup with it should be converted into a BLOB.
This is where DBMS_LOB.CONVERTTOBLOB comes in.
If you need a function that returns a BLOB you have to wrap CONVERTTOBLOB into your own function.
For example:
CREATE OR REPLACE FUNCTION clob_to_blob (p_clob CLOB, p_charsetname VARCHAR2)
RETURN BLOB
AS
l_lang_ctx INTEGER := DBMS_LOB.default_lang_ctx;
l_warning INTEGER;
l_dest_offset NUMBER := 1;
l_src_offset NUMBER := 1;
l_return BLOB;
BEGIN
DBMS_LOB.createtemporary (l_return, FALSE);
DBMS_LOB.converttoblob (
l_return,
p_clob,
DBMS_LOB.lobmaxsize,
l_dest_offset,
l_src_offset,
CASE WHEN p_charsetname IS NOT NULL THEN NLS_CHARSET_ID (p_charsetname) ELSE DBMS_LOB.default_csid END,
l_lang_ctx,
l_warning);
RETURN l_return;
END;
This allows queries like:
SELECT clob_to_blob (field, 'UTF8') FROM t;
To get a list of supported values for the character set name use:
SELECT *
FROM v$nls_valid_values
WHERE parameter = 'CHARACTERSET'
use dbms_lob package for it
for example
select convert(dbms_lob.substr(field,dbms_lob.getlength(field), **0**),
'AL32UTF8',
'WE8ISO8859P15')
from table;
Fixed it:
select convert(dbms_lob.substr(field,dbms_lob.getlength(field)),
'AL32UTF8',
'WE8ISO8859P15')
from table;

Create Oracle XMLTYPE from CLOB specifying character set

I'm trying to create XMLTYPE from CLOB column and specify character set explicitly. I've found there is an overloaded XMLTYPE.createXML function which accepts character set but when I execute passing additional arguments I get an error. Why?
SELECT
XMLTYPE.createXML(TO_CLOB ('<node1><node2>the ´ character</node2></node1>'),NLS_CHARSET_ID('AL32UTF8'),'',1,1)
from dual;
error:
ORA-06553: PLS-306: wrong number or types of arguments in call to
'CREATEXML'
The reason why I bother passing the character set is, CLOB column contains characters which are encoded with different character set than the database character set (which for example doesn't support #180).
The reason why I bother passing the character set is, CLOB column contains characters which are encoded with different character set than the database character set (which for example doesn't support #180).
This one I don't understand. #180; is simple plain ASCII, it should work for any condition.
Simply run
SELECT
XMLTYPE.createXML(TO_CLOB('<node1><node2>the ´ character</node2></node1>'))
from dual;
or even shorter
SELECT
XMLTYPE('<node1><node2>the ´ character</node2></node1>')
from dual;
Now, let's assume your XML contains characters which are not supported by database character set, in this case your XML could be <node1><node2>the ´ character</node2></node1> for example.
First of all you cannot store (or use) any character in CLOB (or VARCHAR2) which is not supported by database character set - never! You must use NCLOB (or NVARCHAR2) which are based on National Database Character Set and typically support any Unicode character.
You can specify character set in XMLTYPE.createXML(), however then you must provide XML as BLOB. You could do it like this:
DECLARE
xmlString NCLOB := '<node1><node2>the '||NCHR(180)||' character</node2></node1>';
xmlDoc XMLTYPE;
xmlBinary BLOB;
lang_context INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
dest_offset INTEGER := 1;
src_offset INTEGER := 1;
read_offset INTEGER := 1;
warning INTEGER;
BEGIN
DBMS_LOB.CREATETEMPORARY(xmlBinary, TRUE);
DBMS_LOB.CONVERTTOBLOB(xmlBinary, xmlString, DBMS_LOB.LOBMAXSIZE, dest_offset, src_offset, 2000, lang_context, warning);
xmlDoc := XMLTYPE.createXML(xmlBinary, 2000, NULL, 1, 1);
END;
2000 is the csid of your national database character set. Use
SELECT PARAMETER, VALUE, NLS_CHARSET_ID(VALUE)
FROM NLS_DATABASE_PARAMETERS
WHERE PARAMETER LIKE '%CHARACTERSET';
to get your ID's.
Some notes:
I tried with string N'<node1><node2>the ´ character</node2></node1>', however Oracle replaced ´ immediately with ¿. I did not manage to enter ´ directly.
Almost all XML Functions return VARCAHR2 values (not NVARCAHR2), also most of XMLTYPE member functions work with CLOB (not NCLOB). If you just read and store your XML documents as XMLTYPE in your database it should be fine, however as soon you start any operations with these data sooner or later you will hit a conversion error. You should really consider to migrate your database character set, see Character Set Migration and/or Oracle Database Migration Assistant for Unicode
You can directly use the XMLType function
SELECT XMLTYPE('<?xml version="1.0" encoding="UTF-8"?>'
||TO_CLOB ('<node1><node2>the ´ character</node2></node1>')) myxml
FROM dual;

How to get around 4000 characters limitation of text_query in Oracle's CONTAINS operator?

In Oracle, the full text search syntax of Contains operator is:
CONTAINS(
[schema.]column,
text_query VARCHAR2
[,label NUMBER]) RETURN NUMBER;
which means the text_query can not be more than 4000 characters long or an error will occur. I repeatedly have text_query longer than 4000 characters long in many cases. How would you, as an Oracle expert, suggest to get around such limitation if possible?
To further clarify the situation in which 4000 is easily reached is that if you combine many Contains Query Operators to construct your text_query, it is quite possible to exceed such 4000 characters limitation.
The 4000 character limit is not some arbitrary boundary: it is the maximum amount of VARCHAR2 characters that Oracle SQL can handle.
4000 characters is a lot of text. In English it's around 600 words, or an A4 page and a bit in a reasonable point font. There are not many applications I can think of which require searching for such large chunks of verbiage. Even colleges checking students' essays for plagiarism would operate at no more than the paragraph level.
However, if you really have a situation in which matching on a scant 4000 characters generates false positives all you can do is split the query string into chunks and search on them. This means you have to use PL/SQL:
create or replace function big_search (p_search_text in varchar2)
return sys_refcursor
is
return_value sys_refcursor;
p_srch1 varchar2(4000);
p_srch2 varchar2(4000);
begin
dbms_output.put_line('search_length='||to_char(length(p_search_text)));
p_srch1 := substr(p_search_text, 1, 4000);
p_srch2 := substr(p_search_text, 4001, 4000);
open return_value for
select docname
, (score(1) + score(2))/2 as score
from t23
where contains ( text_column, p_srch1 , 1) != 0
and contains ( text_column, p_srch2 , 2) != 0;
return return_value;
end;
/
If you don't know the size of the search text beforehand, then you'll need to use dynamic SQL to assemble this. Note that passing null search terms to CONTAINS() will hurl DRG-50901: text query parser syntax error.
The current version supports now a CLOB parameter
CONTAINS(
[schema.]column,
text_query [VARCHAR2|CLOB]
[,label NUMBER])
RETURN NUMBER;
http://docs.oracle.com/cd/B28359_01/text.111/b28304/csql.htm#i997503

Making a sha1-hash of a row in Oracle

I'm having a problem with making a sha1-hash of a row in a select on an Oracle database. I've done it in MSSQL as follows:
SELECT *,HASHBYTES('SHA1',CAST(ID as varchar(10)+
TextEntry1+TextEntry2+CAST(Timestamp as varchar(10)) as Hash
FROM dbo.ExampleTable
WHERE ID = [foo]
However, I can't seem to find a similar function to use when working with Oracle.
As far as my googling has brought me, I'm guessing dbms_crypto.hash_sh1 has something to do with it, but I haven't been able to wrap my brain around it yet...
Any pointers would be greatly appreciated.
The package DBMS_CRYPTO is the correct package to generate hashes. It is not granted to PUBLIC by default, you will have to grant it specifically (GRANT EXECUTE ON SYS.DBMS_CRYPTO TO user1).
The result of this function is of datatype RAW. You can store it in a RAW column or convert it to VARCHAR2 using the RAWTOHEX or UTL_ENCODE.BASE64_ENCODE functions.
The HASH function is overloaded to accept three datatypes as input: RAW, CLOB and BLOB. Due to the rules of implicit conversion, if you use a VARCHAR2 as input, Oracle will try to convert it to RAW and will most likely fail since this conversion only works with hexadecimal strings.
If you use VARCHAR2 then, you need to convert the input to a binary datatype or a CLOB, for instance :
DECLARE
x RAW(20);
BEGIN
SELECT sys.dbms_crypto.hash(utl_raw.cast_to_raw(col1||col2||to_char(col3)),
sys.dbms_crypto.hash_sh1)
INTO x
FROM t;
END;
you will find additional information in the documentation of DBMS_CRYPTO.hash
The DBMS_crypto package does not support varchar2. It works with raw type so if you need a varchar2 you have to convert it. Here is a sample function showing how to do this :
declare
p_string varchar2(2000) := 'Hello world !';
lv_hash_value_md5 raw (100);
lv_hash_value_sh1 raw (100);
lv_varchar_key_md5 varchar2 (32);
lv_varchar_key_sh1 varchar2 (40);
begin
lv_hash_value_md5 :=
dbms_crypto.hash (src => utl_raw.cast_to_raw (p_string),
typ => dbms_crypto.hash_md5);
-- convert into varchar2
select lower (to_char (rawtohex (lv_hash_value_md5)))
into lv_varchar_key_md5
from dual;
lv_hash_value_sh1 :=
dbms_crypto.hash (src => utl_raw.cast_to_raw (p_string),
typ => dbms_crypto.hash_sh1);
-- convert into varchar2
select lower (to_char (rawtohex (lv_hash_value_sh1)))
into lv_varchar_key_sh1
from dual;
--
dbms_output.put_line('String to encrypt : '||p_string);
dbms_output.put_line('MD5 encryption : '||lv_varchar_key_md5);
dbms_output.put_line('SHA1 encryption : '||lv_varchar_key_sh1);
end;
Just to put it here, if someone will search for.
In Oracle 12 you can use standard_hash(<your_value>, <algorythm>) function.
With no parameter <algorythm> defined, it will generate SHA-1 hash (output datatype raw(20))
You can define this function in your favorite package, I defined in utils_pkg.
FUNCTION SHA1(STRING_TO_ENCRIPT VARCHAR2) RETURN VARCHAR2 AS
BEGIN
RETURN LOWER(TO_CHAR(RAWTOHEX(SYS.DBMS_CRYPTO.HASH(UTL_RAW.CAST_TO_RAW(STRING_TO_ENCRIPT), SYS.DBMS_CRYPTO.HASH_SH1))));
END SHA1;
Now to call it
SELECT UTILS_PKG.SHA1('My Text') AS SHA1 FROM DUAL;
The response is
SHA1
--------------------------------------------
5411d08baddc1ad09fa3329f9920814c33ea10c0
You can select a column from some table:
SELECT UTILS_PKG.SHA1(myTextColumn) FROM myTable;
Enjoy!
Oracle 19c:
select LOWER(standard_hash('1234')) from dual;
which is equivalent to
select LOWER(standard_hash('1234','SHA1')) from dual;
will return an SHA1 hash.
For alternative algorithms see: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/STANDARD_HASH.html

Resources