Skip to Main Content
  • Questions
  • Updating columns in tables having million of records

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question.

Asked: March 09, 2017 - 12:12 pm UTC

Last updated: March 11, 2017 - 1:58 am UTC

Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

Viewed 10K+ times! This question is

You Asked

Hi,

I have gone through your forums on how to update a table with millions of records

Approach 1 - To create a temporary table and make the necessary changes, drop the original table and rename temporary table to original table. I have not gone by this approach because i'm not sure of the dependencies of the table when it is dropped and moreover the tables are very important and dropping and creating new will not be allowed.

Approach 2 - To use bulk collect with limit and use forall for update. I have written the below script which will be invoked during database upgrade or restore condition.This script runs only during upgrade from application version to another or restoring data from previous application version to new only. So there will not be simultaneous dml operations coming from application. This will be used during pure database migration.

I have written the below script. Kindly validate and tell where i can improve for better performance, logging. In case of rollback, i'm not sure whether i need it because as my records i can update will be good.

I have two tables in which i need to update few columns. For few of the columns, i have created map like structure(key/value) pairs from which data will be populated.

SET SERVEROUTPUT ON;

DECLARE
 E_CATEGORYNAME EVENT.CATEGORY_VALUE%TYPE;   
 E_PRODUCTFAMILY EVENT.PRODUCTFAMILY%TYPE; 
 A_CATEGORYNAME ALARM.CATEGORY_VALUE%TYPE;   
 A_PRODUCTFAMILY ALARM.PRODUCTFAMILY%TYPE;

 -- SELECT THE ROWS FROM EVENT TABLE
 CURSOR C_EVENT IS
 SELECT ROWID, CATEGORY_VALUE, PRODUCTFAMILY, CATEGORY_ORDINAL
 FROM   EVENT WHERE CATEGORY_VALUE IN ('Autonomous AP','Cisco UCS Series','Routers','Switches and Hubs','Wireless Controller');

 -- CREATE OBJECT TYPE TO HOLD THE ROWS FROM EVENT TABLE
 TYPE C_EVENT_TAB IS TABLE OF C_EVENT%ROWTYPE
 INDEX BY BINARY_INTEGER;
 C_EVENT_ROWS C_EVENT_TAB;

 -- SELECT THE ROWS FROM ALARM TABLE
 CURSOR C_ALARM IS
 SELECT ROWID, CATEGORY_VALUE, PRODUCTFAMILY, CATEGORY_ORDINAL
 FROM   ALARM WHERE CATEGORY_VALUE IN ('Autonomous AP','Cisco UCS Series','Routers','Switches and Hubs','Wireless Controller');

 -- CREATE OBJECT TYPE TO HOLD THE ROWS FROM ALARM TABLE
 TYPE C_ALARM_TAB IS TABLE OF C_ALARM%ROWTYPE;
 C_ALARM_ROWS C_ALARM_TAB;

 -- DECLARE OBJECT TYPE TO HOLD MAPPING BETWEEN PRODUCT FAMILY AND DEFAULT CATEGORY
 TYPE PRODUCT_FAMILY_MAP IS TABLE OF EVENT.CATEGORY_VALUE%TYPE INDEX BY VARCHAR2(255);
 PRODUCT_FAMILY_MAPPING PRODUCT_FAMILY_MAP;


 -- DECLARE OBJECT TYPE TO HOLD MAPPING BETWEEN DEFAULT CATEGORY AND ORDINAL VALUE
 TYPE CATEGORY_ORDINAL_MAP IS TABLE OF EVENT.CATEGORY_VALUE%TYPE INDEX BY VARCHAR2(255);
 CATEGORY_ORDINAL_MAPPING CATEGORY_ORDINAL_MAP;
  
 
BEGIN
 -- ADDING ELEMENTS TO THE DEFAULT CATEGORY AND PRODUCT FAMILY MAPPING TABLE
 PRODUCT_FAMILY_MAPPING('Autonomous AP') := 'AP';
 PRODUCT_FAMILY_MAPPING('Cisco UCS Series') := 'Compute Servers';
 PRODUCT_FAMILY_MAPPING('Routers') := 'Switches and Routers';
 PRODUCT_FAMILY_MAPPING('Switches and Hubs') := 'Switches and Routers';
 PRODUCT_FAMILY_MAPPING('Wireless Controller') := 'Controller';

 -- ADDING ELEMENTS TO THE CATEGORY AND ORDINAL MAPPING TABLE
 CATEGORY_ORDINAL_MAPPING('AP') := 1;
 CATEGORY_ORDINAL_MAPPING('Compute Servers') := 20;
 CATEGORY_ORDINAL_MAPPING('Switches and Routers') := 25;
 CATEGORY_ORDINAL_MAPPING('Controller') := 13;  


 DBMS_OUTPUT.PUT_LINE('BEGINNING UPGRADE SCRIPT PRODUCTFAMILYMIGRATION.SQL');

 DBMS_OUTPUT.PUT_LINE('START TIME -:'||TO_CHAR (SYSTIMESTAMP)); 
 -- UPDATE EVENT TABLE DATA
 OPEN C_EVENT;

 LOOP
  FETCH C_EVENT BULK COLLECT INTO C_EVENT_ROWS LIMIT 300;
  EXIT WHEN C_EVENT_ROWS.COUNT = 0;
  FOR INDX IN 1 .. C_EVENT_ROWS.COUNT
  LOOP

     E_CATEGORYNAME := C_EVENT_ROWS(INDX).CATEGORY_VALUE;
     C_EVENT_ROWS(INDX).PRODUCTFAMILY := E_CATEGORYNAME;
     C_EVENT_ROWS(INDX).CATEGORY_VALUE := PRODUCT_FAMILY_MAPPING(E_CATEGORYNAME);
     C_EVENT_ROWS(INDX).CATEGORY_ORDINAL := CATEGORY_ORDINAL_MAPPING(PRODUCT_FAMILY_MAPPING(E_CATEGORYNAME));
     DBMS_OUTPUT.PUT_LINE( 'LOOPING, C_EVENT%ROWCOUNT = ' || C_EVENT%ROWCOUNT ); 
    EXIT WHEN C_EVENT%NOTFOUND;
     BEGIN
    FORALL INDX IN 1 .. C_EVENT_ROWS.COUNT SAVE EXCEPTIONS
    UPDATE "WCSDBA"."EVENT" SET PRODUCTFAMILY = C_EVENT_ROWS(INDX).PRODUCTFAMILY, CATEGORY_VALUE = C_EVENT_ROWS(INDX).CATEGORY_VALUE, CATEGORY_ORDINAL=C_EVENT_ROWS(INDX).CATEGORY_ORDINAL WHERE ROWID = C_EVENT_ROWS(INDX).ROWID;
    EXCEPTION   
         WHEN OTHERS
         THEN   
            DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_STACK);   
            DBMS_OUTPUT.PUT_LINE ('UPDATED ' || SQL%ROWCOUNT || ' rows.');   

            FOR INDX IN 1 .. SQL%BULK_EXCEPTIONS.COUNT   
            LOOP   
        DBMS_OUTPUT.PUT_LINE (   
       'ERROR '   
           || INDX   
           || ' OCCURRED ON INDEX '   
           || SQL%BULK_EXCEPTIONS (INDX).ERROR_INDEX   
           || '  WITH ERROR CODE '   
           || SQL%BULK_EXCEPTIONS (INDX).ERROR_CODE);   
          END LOOP;   
     END;
     COMMIT;
  END LOOP;
  COMMIT;
 END LOOP;
 IF C_EVENT%ROWCOUNT = 0 THEN
 DBMS_OUTPUT.PUT_LINE('NO DATA PRESENT IN EVENT TABLE FOR MIGRATION. HENCE NOT POPULATING PRODUCT FAMILY COLUMN');
 ELSE 
 DBMS_OUTPUT.PUT_LINE('SUCCESSFULLY UPDATED '||C_EVENT%ROWCOUNT || ' EVENT RECORDS');
 END IF;
 CLOSE C_EVENT;      
 
 OPEN C_ALARM;
 
  LOOP
   FETCH C_ALARM BULK COLLECT INTO C_ALARM_ROWS LIMIT 100;
   EXIT WHEN C_ALARM_ROWS.COUNT = 0;
   FOR INDX IN 1 .. C_ALARM_ROWS.COUNT
   LOOP
 
    A_CATEGORYNAME := C_ALARM_ROWS(INDX).CATEGORY_VALUE;
    C_ALARM_ROWS(INDX).PRODUCTFAMILY := A_CATEGORYNAME;
    C_ALARM_ROWS(INDX).CATEGORY_VALUE := PRODUCT_FAMILY_MAPPING(A_CATEGORYNAME);
    C_ALARM_ROWS(INDX).CATEGORY_ORDINAL := CATEGORY_ORDINAL_MAPPING(PRODUCT_FAMILY_MAPPING(A_CATEGORYNAME));
    DBMS_OUTPUT.PUT_LINE( 'LOOPING, C_ALARM%ROWCOUNT = ' || C_ALARM%ROWCOUNT ); 
   EXIT WHEN C_ALARM%NOTFOUND;
    BEGIN
     FORALL INDX IN 1 .. C_ALARM_ROWS.COUNT SAVE EXCEPTIONS
     UPDATE "WCSDBA"."ALARM" SET PRODUCTFAMILY = C_ALARM_ROWS(INDX).PRODUCTFAMILY, CATEGORY_VALUE = C_ALARM_ROWS(INDX).CATEGORY_VALUE, CATEGORY_ORDINAL=C_ALARM_ROWS(INDX).CATEGORY_ORDINAL WHERE ROWID = C_ALARM_ROWS(INDX).ROWID;
     EXCEPTION   
         WHEN OTHERS
         THEN   
            DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_STACK);   
            DBMS_OUTPUT.PUT_LINE ('UPDATED ' || SQL%ROWCOUNT || ' rows.');   
 
            FOR INDX IN 1 .. SQL%BULK_EXCEPTIONS.COUNT   
            LOOP   
        DBMS_OUTPUT.PUT_LINE (   
       'ERROR '   
           || INDX   
           || ' OCCURRED ON INDEX '   
           || SQL%BULK_EXCEPTIONS (INDX).ERROR_INDEX   
           || '  WITH ERROR CODE '   
           || SQL%BULK_EXCEPTIONS (INDX).ERROR_CODE);   
     END LOOP;   
    END;
    COMMIT;
   END LOOP;
   COMMIT;
  END LOOP;
  IF C_ALARM%ROWCOUNT = 0 THEN
   DBMS_OUTPUT.PUT_LINE('NO DATA PRESENT IN ALARM TABLE FOR MIGRATION. HENCE NOT POPULATING PRODUCT FAMILY COLUMN');
   ELSE 
   DBMS_OUTPUT.PUT_LINE('SUCCESSFULLY UPDATED '||C_ALARM%ROWCOUNT || ' ALARM RECORDS');
  END IF;
 CLOSE C_ALARM;  
 
 DBMS_OUTPUT.PUT_LINE('END TIME -:'||TO_CHAR (SYSTIMESTAMP));

 EXCEPTION WHEN OTHERS THEN
 DBMS_OUTPUT.PUT_LINE('ERROR IN PRODUCTFAMILYMIGRATION.SQL - ENDED' || ':' || SQLERRM) ;
END;
/
SET SERVEROUTPUT OFF;

and Connor said...

A couple of things to consider

1) Your PL/SQL table mappings could just as easily be done with a CASE statement or DECODE, which means you wouldn't need that translation loop, ie, something along the lines of:

UPDATE "WCSDBA"."ALARM" 
SET ... ,
   CATEGORY_VALUE = decode(C_ALARM_ROWS(INDX).CATEGORY_VALUE,'Autonomous AP','AP',etc)


or do the decode/case on the initial cursor definition that does the fetch.

2) Assuming you've taken all the steps you can in terms of improving select/update performance (eg indexing disablement etc), then you might want to consider doing the work in parallel, either with parallel DML or DBMS_PARALLEL_EXECUTE

It strikes me as the entire operation could as simple as:

alter session enable parallel dml;

update /*+ parallel */ EVENT
set 
PRODUCTFAMILY = category_name,
CATEGORY_VALUE = decode(category_namem,'Autonomous AP','AP', etc etc )
CATEGORY_ORDINAL= decode(....)
WHERE CATEGORY_VALUE IN ('Autonomous AP','Cisco UCS Series','Routers','Switches and Hubs','Wireless Controller');


and similarly for ALARM

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Updating millions of records - Alternative approach

Swarna, March 10, 2017 - 2:58 pm UTC

Hi,

Thank you so much for your timely help. The below suggested query worked as expected and was able to update millions of records.

I would like to know as I mentioned in my question. How to choose between various approaches that are suggested for updating millions of records.

I have gone by BULK COLLECT and FORALL approach, but the below code works even better. How do we choose between 1) Temporary table approach 2)n bulk collect and for all approach 3) and the below code
Connor McDonald
March 11, 2017 - 1:58 am UTC

Basically you choose which best suits your business requirements. Some examples

1) If there is potentially invalid data, then you might go for bulk collect/forall, because that way you can still commit those records that *did* succeed. SQL is more of an "all-or-nothing" operation.

2) If you have cross-database operations in play, then you might choose to drag stuff from the remote database into a local temporary table and then use that, to guard against network latency.

3) if you've got lots of server horsepower, then parallel (either with SQL or with DBMS_PARALLEL_EXECUTE) might be a perfect fit.

etc etc

Choose what works best for you. If possible I'll normally go with simple SQL, because its...well...simple :-)

More to Explore

Backup/Recovery

Check out the complete guide to all of the Backup & Recovery techniques in the Oracle Database.