Thursday, May 9, 2013

Bulk Collect – Limit Clause

Consider yourselves in a position to do a data conversion for a table which has more than 500,000 records: The query may be simple solution but what if you want to do a join with another huge table, what if the table has a child table which will need data conversion too, What if you want to do the data conversion in a controlled manner? The solution is to do the data conversion in batches of either 1000 or 10000 records. And doing a commit for every batch, for this BULK COLLECT –LIMIT clause comes in handy.

DECLARE

  CURSOR cur_empno IS
    SELECT DISTINCT em.empno,
                    ch.deptno || '.'|| em.em_empno   new_empno
      FROM emp em, dept ch
     WHERE 1 = 1
       AND em.deptno = ch.deptno
       AND em_empno <> ch.deptno || '.'|| em.em_empno;
   
     TYPE typ_em_empno IS TABLE OF emp.empno%TYPE;
     TYPE typ_em_empno_new IS TABLE OF emp.empno%TYPE;

     larr_em_empno typ_em_empno;
     larr_em_empno_new typ_em_empno_new;

     l_count  PLS_INTEGER := 0;
     ln_limit PLS_INTEGER := 10000;

     ssql VARCHAR2(500);
BEGIN
  OPEN cur_empno;
  LOOP
    FETCH cur_empno BULK COLLECT
      INTO larr_em_empno, larr_em_empno_new LIMIT ln_limit;
    BEGIN
   
      sp_insert_log('Total in Cursor: ' || larr_em_empno.COUNT);
   
      ssql := 'Emp Update';
   
      FORALL i IN 1 .. larr_em_empno.COUNT SAVE EXCEPTIONS
        UPDATE emp
           SET empno = larr_em_empno_new(i)
         WHERE empno = larr_em_empno(i);
   
      sp_insert_log(ssql || ' Total rows processed = ' || SQL%ROWCOUNT);
   
      ssql := 'EntryID Update';
   
      FORALL i IN 1 .. larr_em_empno.COUNT SAVE EXCEPTIONS
        UPDATE emp_det
           SET empno = larr_em_empno_new(i)
         WHERE empno = larr_em_empno(i);
    
      sp_insert_log(ssql || ' Total rows processed = ' || SQL%ROWCOUNT);
   
      FORALL i IN 1 .. larr_em_empno.COUNT SAVE EXCEPTIONS
        UPDATE emp_addr
           SET empno = larr_em_empno_new(i)
         WHERE empno = larr_em_empno(i);
   
      sp_insert_log(ssql || ' Total rows processed = ' || SQL%ROWCOUNT);
         
      ssql := 'Update_empno_Log Insert';
   
      FORALL i IN 1 .. larr_em_empno.COUNT SAVE EXCEPTIONS
        INSERT INTO update_empno_log
        VALUES
          (larr_em_empno(i), larr_em_empno_new(i), SYSDATE);
   
      sp_insert_log(ssql || ' Total rows processed = ' || SQL%ROWCOUNT);
   
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK;
        sp_insert_log(ssql || 'Total No of Errors:' ||
                      SQL%BULK_EXCEPTIONS.COUNT);
        FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
        LOOP

          sp_insert_log('Error #:' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX ||
                        ' Err code:' || SQL%BULK_EXCEPTIONS(i).ERROR_CODE ||
                        ' empno:' || larr_em_empno(i) ||
                        ' Err Msg:' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));  
                           
        END LOOP;
        RAISE;
    END;
    COMMIT;
    EXIT WHEN cur_empno%NOTFOUND;
  END LOOP;
  CLOSE cur_empno;

EXCEPTION
  WHEN OTHERS THEN
    sp_insert_log('ERROR' || SQLCODE || ' ' || substr(SQLERRM, 1, 200));
    dbms_output.put_line('ERROR' || SQLCODE || ' ' ||
                         substr(SQLERRM, 1, 200));
END;

Having the records in cursor will avoid the over head of query cost by join. Committing in batches helps us, as the data only commits when the child table is also converted for that batch.
Supporting tables for the above script

create table update_track_Log
(
  ROWNO   number not null,
  TEXT    varchar2(4000),
  UPDATED date default SYSDATE not null
);

create table Update_empno_Log
(
  em_empno        VARCHAR2(30),
  em_empno_new    VARCHAR2(30),
  UPDATED date default SYSDATE not null
)

Thursday, May 2, 2013

Bulk Grant

How do you grant permission to all objects in a schema to other?

DECLARE
  sschema VARCHAR2(50) := 'SCOTT';
BEGIN
  FOR r1 IN (SELECT object_name, object_type
               FROM user_objects
              WHERE object_type IN ('TABLE', 'VIEW'))
  LOOP
    EXECUTE IMMEDIATE 'grant select, insert, delete, update on ' ||
                      r1.object_name || ' to ' || sschema;
  END LOOP;
END;
/


DECLARE
  sschema VARCHAR2(50) := 'SCOTT';
BEGIN
  FOR r1 IN (SELECT object_name, object_type
               FROM user_objects
              WHERE object_type IN ('PROCEDURE'))
  LOOP
    EXECUTE IMMEDIATE 'grant execute on ' || r1.object_name || ' to ' ||
                      sschema;
  END LOOP;
END;
/

Tuesday, November 6, 2012

BULK_EXCEPTIONS

SQL%BULK_EXCEPTIONS

SQL%BULK_EXCEPTIONS argument saves all the exceptions raised during the bulk operation provided FORALL statement should have SAVE EXCEPTIONS added.

SQL%BULK_EXCEPTIONS.count gives the number of exceptions raised in the last FORALL bulk opearation

SQL%BULK_EXCEPTIONS.ERROR_INDEX gives the iteration number in which the exception is raised

SQL%BULK_EXCEPTIONS.ERROR_CODE gives the error code, using which we can find the error message as ERRM( - SQL%BULK_EXCEPTIONS ).

Sample (Using Emp table)

Without using SAVE EXCEPTIONS

DECLARE
  TYPE larr_typ_emp IS TABLE OF NUMBER;
  larr_emp larr_typ_emp := larr_typ_emp(7782, 7839, 7934, 7566, 7788, 7876,
                                        7369, 7499, 7521, 7844, 7900, 7698,
                                        7654, 7902);
BEGIN
  FORALL i IN larr_emp.FIRST .. larr_emp.LAST
    UPDATE emp SET job = job || 'JOB' WHERE empno = larr_emp(i);

EXCEPTION
  WHEN OTHERS THEN
    dbms_output.put_line('Total No of Errors:' ||
                         SQL%BULK_EXCEPTIONS.COUNT);
    FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
    LOOP
      dbms_output.put_line('index:' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
      dbms_output.put_line('code:' || SQL%BULK_EXCEPTIONS(i).ERROR_CODE);
      dbms_output.put_line('message (Without -):' ||
                           SQLERRM(sql%BULK_EXCEPTIONS(i).ERROR_CODE));
      dbms_output.put_line('message (With -):' ||
                           SQLERRM(-sql%BULK_EXCEPTIONS(i).ERROR_CODE));
    END LOOP;
END;
/

Out put would be

Total No of Errors:1
index:1
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column "AISDEV"."EMP"."JOB" (actual: 10, maximum: 9)


Using SAVE EXCEPTIONS

DECLARE
  TYPE larr_typ_emp IS TABLE OF NUMBER;
  larr_emp larr_typ_emp := larr_typ_emp(7782, 7839, 7934, 7566, 7788, 7876,
                                        7369, 7499, 7521, 7844, 7900, 7698,
                                        7654, 7902);
BEGIN
  FORALL i IN larr_emp.FIRST .. larr_emp.LAST SAVE EXCEPTIONS
    UPDATE emp SET job = job || 'JOB' WHERE empno = larr_emp(i);

EXCEPTION
  WHEN OTHERS THEN
    dbms_output.put_line('Total No of Errors:' ||
                         SQL%BULK_EXCEPTIONS.COUNT);
    FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
    LOOP
      dbms_output.put_line('index:' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
      dbms_output.put_line('code:' || SQL%BULK_EXCEPTIONS(i).ERROR_CODE);
      dbms_output.put_line('message (Without -):' ||
                           SQLERRM(SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
      dbms_output.put_line('message (With -):' ||
                           SQLERRM(-sql%BULK_EXCEPTIONS(i).ERROR_CODE));
    END LOOP;
END;
/

Out put would be


Total No of Errors:10
index:1
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:2
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:4
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:5
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:8
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:9
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:10
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:12
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:13
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )
index:14
code:12899
message (Without -): -12899: non-ORACLE exception
message (With -):ORA-12899: value too large for column  (actual: , maximum: )