Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Oracle SQL - How to strip leading bytes from BLOB (and return as RAW)?

I'm writing a query for a reporting tool (Pentaho Report Designer) where I need to retrieve some jpgs that are stored in the Oracle 11 database. The trick is that the jogs (stored as BLOBs) include a 12 byte header (added by another application) that I need to remove. The reporting tool also requires the data to be returned as RAW data.

I am running into problems where it appears that the functions/procedures that I have found for manipulating BLOBs all have prohibitive size/length restrictions on them.

This query is as close to working as I can get it, using DBMS_LOB.SUBSTR(dbfile.filedata,2000,12):

select DBMS_LOB.SUBSTR(dbfile.filedata,2000,12) as filedata
from bms_0002005_251 safety
inner join bms_9999999_100 file02 on safety.bms_id = file02.bms_fk_0002005_839_ID
inner join bms_9999999_104 inc on safety.bms_fk_0002005_844_id = inc.bms_id
left join bms_dbfiles dbfile on file02.bms_9999999_40 = dbfile.uniqueid

For images <= 2000 bytes, this works perfectly, strips the 12 byte header and returns raw data like FFD8FFE000104A46494600010201006000600000FFEE000E41646F626500640000000001... etc

But for larger images (most of the images) 2000 is not enough, but as soon as I increase the substring length to 2001, the query fails:

ORA-06502: PL/SQL: numeric or value error: raw variable length too long ORA-06512: at line 1 06502. 00000 - "PL/SQL: numeric or value error%s"

This is the closest I have got, but long story short - is there any way in a single query I can strip the first 12 bytes from a large BLOB and return the data as RAW?

like image 492
whitegoose Avatar asked May 03 '17 06:05

whitegoose


People also ask

How do you convert BLOB to long Raw?

You can to convert Existing column data with blob data type to long raw data. v_length := dbms_lob. GETLENGTH(v_blob_update); -- Set the amount (bytes) to be converted equal to the length of the blob.

How do you extract blob from a table and save it as a file?

First we create a directory object pointing to the destination directory. CONN / AS SYSDBA CREATE OR REPLACE DIRECTORY BLOBS AS '/tmp/'; GRANT READ, WRITE ON DIRECTORY BLOBS TO my_user; Next we open the BLOB, read chunks into a buffer and write them to a file. Finally, you can check the file is produced correctly.

What does DBMS_ LOB do?

The DBMS_LOB package provides subprograms to operate on BLOBs, CLOBs, and NCLOBs. You can use DBMS_LOB to access and manipulate specific parts of LOBs or complete LOBs. You can also refer to "Large objects (LOBs)" in Oracle TimesTen In-Memory Database PL/SQL Developer's Guide.


2 Answers

Just create a user defined function to do this for you and use it in the select.

I whipped one simple example that does this by simply having the first offset at 13 instead of 1:

create or replace function strip12(p_blob in blob) return blob is
  pragma autonomous_transaction;
  l_length  pls_integer;
  l_loc     pls_integer;
  l_buffer  pls_integer := 2000;
  l_newblob blob;
begin
  dbms_lob.createtemporary(lob_loc => l_newblob, cache => true);
  l_length := dbms_lob.getlength(p_blob);

  l_loc := 13;
  while l_loc <= l_length loop
    dbms_lob.append(dest_lob => l_newblob, src_lob => dbms_lob.substr(lob_loc => p_blob, amount => l_buffer, offset => l_loc));
    l_loc := l_loc + l_buffer;
  end loop;
  return l_newblob;
end;

Then you simply use this in your select

select strip12(dbfile.filedata) as filedata
  from bms_0002005_251 safety
 inner join bms_9999999_100 file02
    on safety.bms_id = file02.bms_fk_0002005_839_ID
 inner join bms_9999999_104 inc
    on safety.bms_fk_0002005_844_id = inc.bms_id
  left join bms_dbfiles dbfile
    on file02.bms_9999999_40 = dbfile.uniqueid

Regards

like image 63
Olafur Tryggvason Avatar answered Oct 26 '22 04:10

Olafur Tryggvason


Generally speaking, it is not possible to modify a blob on-the-fly in a query, considering ORA-14553. Blob variable is a pointer to data, but not the data itself. That's why functions like DBMS_LOB.SUBSTR return not BLOB or CLOB, but RAW or CHAR - since data (variables) of the last types are directly available in RAM.

Thus to query modified blob (with 12 bytes cut off from the head) we need to create and store the modified blob before querying. Depending on business requirements it can be done in place or by creating new blobs, preserving originals. In the case of the question I suppose we cannot modify original blobs in place.

Obviously, the second way (preserving originals) is more resource-intensive way.

Big picture of solution:

  1. Determine a list of blobs you are going to query
  2. For each item in the list create and store a modified blob (e.g. with trimmed 12 bytes) with link by id to the original
  3. Run a query returning modified blobs
  4. Remove modified blobs (immediately after the query cursor is consumed OR by schedule)

This causes more problems than it solves:

  • need for cleanup of modified blobs
  • synchronization of original and modified
  • doubling the amount of stored data
  • other surprises

I guess there is a less painful solution - write a code in the outer party consuming the query results and modify blob data outside of the database.

like image 20
diziaq Avatar answered Oct 26 '22 05:10

diziaq