简体   繁体   中英

character string buffer too small ORA-06502

I am having a problem while concatenating the varchar2 datatype in a cursor loop.

Procedure is iterating in a loop to build the in clause for insert and delete operations in batch.The process will run in batch for every 1000 account numbers.

For small amount of records it works but when it tries to concatenate large amount of records(36451477 in temp table) in a loop it throws.

java.sql.SQLException: ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at "QA01BT.LOAD_ITEM_DATA_TO_CONSOLIDATE", line 23 ORA-06512: at line 1

i have put a max limit of search id to 32767 but still it does not work.

is there any other way to achieve this?

create or replace PROCEDURE LOAD_ITEM_DATA_TO_CONSOLIDATE(updatecount OUT NUMBER
)
IS
  cnt       NUMBER := 0;
  c_limit CONSTANT PLS_INTEGER DEFAULT 1000;
  search_id varchar2(32727);
  TYPE account_array
    IS TABLE OF VARCHAR2(255) INDEX BY BINARY_INTEGER;
  l_data    ACCOUNT_ARRAY;
  CURSOR account_cursor IS
    SELECT DISTINCT account_no AS account_num
    FROM   item_temp;
BEGIN
    OPEN account_cursor;

    LOOP
        FETCH account_cursor bulk collect INTO l_data limit c_limit;

        search_id := '''';

        FOR i IN 1 .. l_data.count LOOP
            IF( i != 1 ) THEN
              search_id := search_id
                           || ','
                           || ''''
                           || l_data(i)
                           || '''';
            ELSE
              search_id := search_id
                           || l_data(i)
                           || '''';
            END IF;
        END LOOP;

        BEGIN

        SAVEPOINT move_data_to_temp_table;

        EXECUTE IMMEDIATE 'delete from item where ACCOUNT_NO IN('||search_id||')';

        EXECUTE IMMEDIATE 'insert into item(ID,ACCOUNT_NO,ITEM_ID,ITEM_VALUE) select HIBERNATE_SEQUENCE.nextval,temp.ACCOUNT_NO,temp.ITEM_ID,temp.ITEM_VALUE from item_TEMP temp     where ACCOUNT_NO IN('||search_id||')';

        cnt := cnt + SQL%rowcount;

        COMMIT;

        EXCEPTION WHEN OTHERS THEN ROLLBACK to move_data_to_temp_table;

        END;

        EXIT WHEN account_cursor%NOTFOUND;

    END LOOP;

    updatecount := cnt;

    CLOSE account_cursor;

END LOAD_ITEM_DATA_TO_CONSOLIDATE;

This seems somewhat over-engineered. Why not just this?

create or replace PROCEDURE LOAD_ITEM_DATA_TO_CONSOLIDATE
    (updatecount OUT NUMBER)
IS
BEGIN
    delete from item 
    where ACCOUNT_NO IN ( SELECT account_no
                          FROM   item_temp);

   insert into item(ID,ACCOUNT_NO,ITEM_ID,ITEM_VALUE) 
   select HIBERNATE_SEQUENCE.nextval, temp.ACCOUNT_NO, temp.ITEM_ID, temp.ITEM_VALUE 
   from item_TEMP temp  ;

    updatecount := SQL%rowcount;

END LOAD_ITEM_DATA_TO_CONSOLIDATE;

If you do decide you need to do this in batches and are worried about that string getting too long or having too many elements in the list (max is 1000), you should try putting your values into an array and then using IN against the array, via a table function or a direct reference to the table.

Extra bonus: no need for dynamic SQL!

Something like this:

CREATE OR REPLACE TYPE strings_t IS TABLE OF VARCHAR2 (255)
/

CREATE OR REPLACE PROCEDURE load_item_data_to_consolidate (
   updatecount   OUT NUMBER)
IS
   cnt                NUMBER := 0;
   c_limit   CONSTANT PLS_INTEGER DEFAULT 1000;
   l_data             strings_t;

   CURSOR account_cursor
   IS
      SELECT DISTINCT account_no AS account_num FROM item_temp;
BEGIN
   OPEN account_cursor;

   LOOP
      FETCH account_cursor BULK COLLECT INTO l_data LIMIT c_limit;

      BEGIN
         SAVEPOINT move_data_to_temp_table;

         DELETE FROM item
               WHERE account_no IN (SELECT COLUMN_VALUE FROM TABLE (l_data));

         INSERT INTO item (id,
                           account_no,
                           item_id,
                           item_value)
            SELECT hibernate_sequence.NEXTVAL,
                   temp.account_no,
                   temp.item_id,
                   temp.item_value
              FROM item_temp temp
             WHERE account_no IN (SELECT COLUMN_VALUE FROM TABLE (l_data));

         cnt := cnt + SQL%ROWCOUNT;

         COMMIT;
      EXCEPTION
         WHEN OTHERS
         THEN
            ROLLBACK TO move_data_to_temp_table;
      END;

      EXIT WHEN account_cursor%NOTFOUND;
   END LOOP;
END;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM