Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call REPLACE with CLOB (without exceeding 32K)

Oracle 11g has certainly improved usability of CLOBs, having overloaded most of the string functions so they now work natively with CLOBs.

However, a colleague was getting this error from his code:

ORA-22828: input pattern or replacement parameters exceed 32K size limit
22828. 00000 -  "input pattern or replacement parameters exceed 32K size limit"
*Cause:    Value provided for the pattern or replacement string in the form of
           VARCHAR2 or CLOB for LOB SQL functions exceeded the 32K size limit.
*Action:   Use a shorter pattern or process a long pattern string in multiple
           passes.

This only occurred when the third parameter to replace was a CLOB with more than 32k characters.

(Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production)

Test case:

declare
  v2 varchar2(32767);
  cl_small clob;
  cl_big clob;
  cl_big2 clob;
begin
  v2 := rpad('x', 32767, 'x');
  dbms_output.put_line('v2:' || length(v2));
  cl_small := v2;
  dbms_output.put_line('cl_small:' || length(cl_small));
  cl_big := v2 || 'y' || v2;
  dbms_output.put_line('cl_big[1]:' || length(cl_big));
  cl_big2 := replace(cl_big, 'y', cl_small);
  dbms_output.put_line('cl_big[2]:' || length(cl_big2));
  cl_big2 := replace(cl_big, 'y', cl_big); 
  dbms_output.put_line('cl_big[3]:' || length(cl_big2));
end;
/

Results:

v2:32767
cl_small:32767
cl_big[1]:65535
cl_big[2]:98301
ORA-22828: input pattern or replacement parameters exceed 32K size limit

This seems at odds with the docs which imply that the replacement string may be a CLOB - I would have thought this should imply that any CLOB would be allowed, not just those that happen to be <32K: http://docs.oracle.com/cd/E11882_01/server.112/e41084/functions153.htm#SQLRF00697

like image 719
Jeffrey Kemp Avatar asked Apr 17 '14 06:04

Jeffrey Kemp


2 Answers

Here is a rough first draft for a function that will do the job with certain limitations, it hasn't been very well tested yet:

function replace_with_clob
  (i_source in clob
  ,i_search in varchar2
  ,i_replace in clob
  ) return clob is
  l_pos pls_integer;
begin
  l_pos := instr(i_source, i_search);
  if l_pos > 0 then
    return substr(i_source, 1, l_pos-1)
        || i_replace
        || substr(i_source, l_pos+length(i_search));
  end if;
  return i_source;
end replace_with_clob;

It only does a single replace on the first instance of the search term.

declare
  v2 varchar2(32767);
  cl_small clob;
  cl_big clob;
  cl_big2 clob;
begin
  v2 := rpad('x', 32767, 'x');
  dbms_output.put_line('v2:' || length(v2));
  cl_small := v2;
  dbms_output.put_line('cl_small:' || length(cl_small));
  cl_big := v2 || 'y' || v2;
  dbms_output.put_line('cl_big[1]:' || length(cl_big));
  cl_big2 := replace(cl_big, 'y', cl_small);
  dbms_output.put_line('cl_big[2]:' || length(cl_big2));
  cl_big2 := replace_with_clob(cl_big, 'y', cl_big); 
  dbms_output.put_line('cl_big[3]:' || length(cl_big2));
end;
/

v2:32767
cl_small:32767
cl_big[1]:65535
cl_big[2]:98301
cl_big[3]:131069
like image 133
Jeffrey Kemp Avatar answered Oct 07 '22 09:10

Jeffrey Kemp


You can create a function to handle CLOB values of any length:

SQL Fiddle

CREATE FUNCTION lob_replace(
  i_lob    IN clob, 
  i_what   IN varchar2, 
  i_with   IN clob,
  i_offset IN INTEGER DEFAULT 1,
  i_nth    IN INTEGER DEFAULT 1
) RETURN CLOB
AS
  o_lob  CLOB;
  n      PLS_INTEGER;
  l_lob  PLS_INTEGER;
  l_what PLS_INTEGER;
  l_with PLS_INTEGER;
BEGIN
  IF   i_lob IS NULL
    OR i_what IS NULL
    OR i_offset < 1
    OR i_offset > DBMS_LOB.LOBMAXSIZE
    OR i_nth < 1
    OR i_nth > DBMS_LOB.LOBMAXSIZE
  THEN
    RETURN NULL;
  END IF;

  n      := NVL( DBMS_LOB.INSTR( i_lob, i_what, i_offset, i_nth ), 0 );
  l_lob  := DBMS_LOB.GETLENGTH( i_lob );
  l_what := LENGTH( i_what );
  l_with := NVL( DBMS_LOB.GETLENGTH( i_with ), 0 );

  DBMS_LOB.CREATETEMPORARY( o_lob, FALSE );
  IF n > 0 THEN
    IF n > 1 THEN
      DBMS_LOB.COPY( o_lob, i_lob, n-1, 1, 1 );
    END IF;

    IF l_with > 0 THEN
      DBMS_LOB.APPEND( o_lob, i_with ); 
    END IF;

    IF n + l_what <= l_lob THEN
      DBMS_LOB.COPY( o_lob, i_lob, l_lob - n - l_what + 1, n + l_with, n + l_what );
    END IF;
  ELSE
    DBMS_LOB.APPEND( o_lob, i_lob );
  END IF;
  RETURN o_lob;
END; 
/

Oracle 11g R2 Schema Setup:

CREATE TABLE table_name ( value clob)
/

CREATE TABLE replacements ( str VARCHAR2(4000), repl CLOB )
/

DECLARE
  str VARCHAR2(4000) := 'value';
  r   CLOB;
  c1l CLOB;
  c1m CLOB;
  c1r CLOB;
  c2l CLOB;
  c2m CLOB;
  c2r CLOB;
  c3l CLOB;
  c3m CLOB;
  c3r CLOB;
BEGIN
  DBMS_LOB.CREATETEMPORARY( r, FALSE );
  DBMS_LOB.CREATETEMPORARY( c1l, FALSE );
  DBMS_LOB.CREATETEMPORARY( c1m, FALSE );
  DBMS_LOB.CREATETEMPORARY( c1r, FALSE );
  DBMS_LOB.CREATETEMPORARY( c2l, FALSE );
  DBMS_LOB.CREATETEMPORARY( c2m, FALSE );
  DBMS_LOB.CREATETEMPORARY( c2r, FALSE );
  DBMS_LOB.CREATETEMPORARY( c3l, FALSE );
  DBMS_LOB.CREATETEMPORARY( c3m, FALSE );
  DBMS_LOB.CREATETEMPORARY( c3r, FALSE );
  FOR i IN 1 .. 10 LOOP
    DBMS_LOB.WRITEAPPEND( r, 4000, RPAD( 'y', 4000, 'y' ) );
    DBMS_LOB.WRITEAPPEND( C1m, 20, RPAD( 'x', 20, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C1r, 40, RPAD( 'x', 40, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C2m, 200, RPAD( 'x', 200, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C2r, 400, RPAD( 'x', 400, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C3m, 2000, RPAD( 'x', 2000, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C3r, 4000, RPAD( 'x', 4000, 'x' ) );
  END LOOP;
  DBMS_LOB.WRITEAPPEND( c1l, 5, str );
  DBMS_LOB.WRITEAPPEND( c1m, 5, str );
  DBMS_LOB.WRITEAPPEND( c1r, 5, str );
  DBMS_LOB.WRITEAPPEND( c2l, 5, str );
  DBMS_LOB.WRITEAPPEND( c2m, 5, str );
  DBMS_LOB.WRITEAPPEND( c2r, 5, str );
  DBMS_LOB.WRITEAPPEND( c3l, 5, str );
  DBMS_LOB.WRITEAPPEND( c3m, 5, str );
  DBMS_LOB.WRITEAPPEND( c3r, 5, str );
  FOR i IN 1 .. 10 LOOP
    DBMS_LOB.WRITEAPPEND( C1l, 40, RPAD( 'x', 40, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C1m, 20, RPAD( 'x', 20, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C2l, 400, RPAD( 'x', 400, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C2m, 200, RPAD( 'x', 200, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C3l, 4000, RPAD( 'x', 4000, 'x' ) );
    DBMS_LOB.WRITEAPPEND( C3m, 2000, RPAD( 'x', 2000, 'x' ) );
  END LOOP;
  INSERT INTO table_name VALUES ( NULL );
  INSERT INTO table_name VALUES ( EMPTY_CLOB() );
  INSERT INTO table_name VALUES ( '0123456789' );
  INSERT INTO table_name VALUES ( str );
  INSERT INTO table_name VALUES ( c1l );
  INSERT INTO table_name VALUES ( c1m );
  INSERT INTO table_name VALUES ( c1r );
  INSERT INTO table_name VALUES ( c2l );
  INSERT INTO table_name VALUES ( c2m );
  INSERT INTO table_name VALUES ( c2r );
  INSERT INTO table_name VALUES ( c3l );
  INSERT INTO table_name VALUES ( c3m );
  INSERT INTO table_name VALUES ( c3r );
  INSERT INTO replacements VALUES ( str, r );
  COMMIT;
END;
/

Query 1:

SELECT DBMS_LOB.GETLENGTH( value )
FROM   table_name

Results:

| DBMS_LOB.GETLENGTH(VALUE) |
|---------------------------|
|                    (null) |
|                         0 |
|                        10 |
|                         5 |
|                       405 |
|                       405 |
|                       405 |
|                      4005 |
|                      4005 |
|                      4005 |
|                     40005 |
|                     40005 |
|                     40005 |

Query 2:

UPDATE table_name
SET value = LOB_REPLACE(
              value,
              ( SELECT str FROM replacements ),
              ( SELECT repl FROM replacements )
            )

Query 3:

SELECT DBMS_LOB.GETLENGTH( value )
FROM   table_name

Results:

| DBMS_LOB.GETLENGTH(VALUE) |
|---------------------------|
|                    (null) |
|                         0 |
|                        10 |
|                     40000 |
|                     40400 |
|                     40400 |
|                     40400 |
|                     44000 |
|                     44000 |
|                     44000 |
|                     80000 |
|                     80000 |
|                     80000 |
like image 2
MT0 Avatar answered Oct 07 '22 08:10

MT0