One way to get a smaller CLOB from another CLOB is to follow the extract CLOB from JSON example: use DBMS_LOB INSTR/SUBSTR functions to obtain the CLOB fragment of interest. We could look at the same problem from another angle: delete the CLOB fragments *not* of interest, retaining only what we want, with DBMS_LOB.FRAGMENT_DELETE.
Deleting CLOB fragments would be apt for your use case if:
- the CLOB persists in the database
- the fragment of interest forms the bulk of the CLOB data
- it is not necessary to retain the original CLOB i.e. the fragment of interest can update the same attribute that holds the original CLOB data
Here’s how to crop a CLOB from either end, using the procedure DBMS_LOB.FRAGMENT_DELETE.
CLOB Data Example and Cropping Needs
Consider the same CLOB data and "payload" extraction requirement as in the article on JSON CLOB retrieval.
CLOB Data:
[
{
"id": "1",
"payloadType": "csv",
"payload": "Very big payload 100K bytes"
}
]
To extract:
Very big payload 100K bytes
DBMS_LOB.FRAGMENT_DELETE Solution
Approach:
- Select the CLOB data FOR UPDATE
- Use dbms_lob.instr to find the start/end/length of the payload, based on the input keyname and delimiters
- Delete the pre- fragment: from character position 1 till the start of the payload
- Delete the post- fragment: from character position just after the length of the payload, till the end of the CLOB data
- Commit the transaction in the caller if the fragments have been deleted successfully
crop_clob function:
-- Function that takes as input (1) an id to pick CLOB data
-- for update (2) key name based on which CLOB fragments
-- are to be deleted
CREATE OR REPLACE FUNCTION crop_clob
(p_clob_ref_id IN NUMBER
, p_keyname IN VARCHAR2)
RETURN BOOLEAN
IS
l_payload CLOB := empty_clob();
l_payload_init PLS_INTEGER;
l_payload_end PLS_INTEGER;
l_payload_size PLS_INTEGER;
l_orig_clob_size PLS_INTEGER;
l_keyname VARCHAR2(100) := '"'||p_keyname||'":';
BEGIN
-- Select the CLOB data FOR UPDATE
select data into l_payload
from test_clobdata
where id = p_clob_ref_id
for update;
l_orig_clob_size := dbms_lob.getlength(l_payload);
-- Use dbms_lob.instr to find the start/end/length
-- of the target payload, based on input keyname
-- and delimiters
l_payload_init := dbms_lob.instr(l_payload
, l_keyname)
+ length(l_keyname) + 1;
l_payload_end := dbms_lob.instr(l_payload
, '"', l_payload_init + 1) - 1;
l_payload_size := l_payload_end - l_payload_init;
-- FRAGMENT_DELETE takes as input:
-- (1) the CLOB data
-- (2) the fragment length to be deleted
-- (3) the offset from the start of the CLOB data
-- Delete the pre- fragment: from character position
-- 1 till the start of the payload
dbms_lob.fragment_delete(
l_payload
, l_payload_init
, 1
);
-- Delete the post- fragment: from character position
-- just after the length of the payload, till the end
-- of the end of the CLOB data
dbms_lob.fragment_delete(
l_payload -- CLOB data
, (l_orig_clob_size - l_payload_end)
, l_payload_size + 1
);
RETURN TRUE;
END crop_clob;
When compiled:
SQL> -- Function that takes as input (1) an id to pick CLOB data SQL> -- for update (2) key name based on which CLOB fragments SQL> -- are to be deleted SQL> CREATE OR REPLACE FUNCTION crop_clob 2 (p_clob_ref_id IN NUMBER 3 , p_keyname IN VARCHAR2) 4 RETURN BOOLEAN 5 IS 6 l_payload CLOB; 7 l_payload_init PLS_INTEGER; 8 l_payload_end PLS_INTEGER; 9 l_payload_size PLS_INTEGER; 10 l_keyname VARCHAR2(100) := '"'||p_keyname||'":'; 11 BEGIN 12 13 -- Select the CLOB data FOR UPDATE 14 select data into l_payload 15 from test_clobdata 16 where id = p_clob_ref_id 17 for update; 18 19 -- Use dbms_lob.instr to find the start/end/length 20 -- of the payload, based on input keyname and delimiters 21 -- 22 l_payload_init := dbms_lob.instr(l_payload 23 , l_keyname) 24 + length(l_keyname) + 1; 25 26 -- End position of payload 27 l_payload_end := dbms_lob.instr(l_payload 28 , '"', l_payload_init + 1) - 1; 29 30 l_payload_size := l_payload_end - l_payload_init; 31 32 -- FRAGMENT_DELETE takes as input: 33 -- (1) the CLOB data 34 -- (2) the fragment length to be deleted 35 -- (3) the offset from the start of the CLOB data 36 37 -- Delete the pre- fragment: from character position 1 38 -- till the start of the payload 39 dbms_lob.fragment_delete( 40 l_payload 41 , l_payload_init 42 , 1 43 ); 44 -- Delete the post- fragment: from character position 45 -- just after the length of the payload, till the end 46 -- of the CLOB data 47 dbms_lob.fragment_delete( 48 l_payload -- CLOB data 49 , (l_orig_clob_size - l_payload_end) 50 , l_payload_size + 1 51 ); 52 RETURN TRUE; 53 54 END crop_clob; 55 / Function created.
Testing the Solution: Crop a CLOB with DBMS_LOB.FRAGMENT_DELETE
Table to hold the CLOB data:
SQL> -- Table to hold the CLOB data SQL> desc test_clobdata Name Null? Type ----------------------- -------- ----------- ID NUMBER DATA CLOB
Original CLOB data (before cropping):
SQL> -- Original CLOB data
SQL> select * from test_clobdata;
ID DATA
--- -----------------------------------
1 [
{
"id": "1",
"payloadType": "csv",
"payload": "Test data"
}
]
Call crop_clob in an anonymous block:
SQL> -- Call crop_clob in an anonymous block SQL> begin 2 if (crop_clob(1, 'payload')) then 3 commit; 4 end if; 5 end; 6 / PL/SQL procedure successfully completed.
Cropped CLOB data:
SQL> -- Cropped CLOB data SQL> select * from test_clobdata; ID DATA --- ------------------------------ 1 Test data
Summary
This article shows how to delete fragments from a CLOB, from either end, using DBMS_LOB.FRAGMENT_DELETE. This solution can be used to retain only a part of an CLOB that persists in the database, without having to rewrite all the CLOB data.








