Friday 2 September 2011

Script to Alter varchar2 byte cols to char

 
The script below modifies all columns from VARCHAR2 BYTE to CHAR for all tables listed in the IN list. The script uses the USER_TAB_COLUMNS view. A log table has been created to record failures.

 
create table log_tbl (
  table_name varchar2(30)
, column_name varchar2(30)
, msg varchar2(200)
, error_flag varchar2(1) default 'P') -- P for Pass and F for Fail.
/ 

SQL> select table_name, column_name, char_used
  2  from user_tab_columns
  3  where table_name in ('T1','T2')
  4  / 
 
TABLE_NAME                     COLUMN_NAME                    C
------------------------------ ------------------------------ -
T1                             A                              B
T2                             A                              B
 
SQL> declare
  2    l_Err varchar2(200);
  3  begin
  4    for r in (select  atc.table_name, atc.column_name, atc.data_length
  5              from    user_tab_columns atc -- You would probably use ALL_
  6              left outer join Log_Tbl lt on (atc.Table_name   = lt.Table_Name
  7                                         and atc.Column_name = lt.Column_Name
  8                                         and lt.Error_Flag   = 'P')
  9              where   atc.data_type   = 'VARCHAR2'
 10              and     atc.char_used   = 'B'
 11              and     atc.Table_Name in ('T1', 'T2', 'T3')) loop
 12  
 13      begin 
 14        execute immediate 'alter table ' || r.table_name 
 15                                        || ' modify ' 
 16                                        || r.column_name 
 17                                        || ' varchar2('
 18                                        || r.data_length
 19                                        || ' char)';
 20      
 21        insert into Log_tbl (Table_Name, Column_Name) 
 22        values  (r.Table_Name, r.Column_Name);
 23        
 24        exception
 25          when others then
 26            l_Err := sqlerrm;
 27            insert into Log_tbl (Table_Name, Column_Name, Msg, Error_Flag) 
 28            values  (r.Table_Name, r.Column_Name, l_Err, 'F');
 29      end;
 30      
 31      commit;
 32      
 33    end loop;
 34    
 35  end;
 36  / 
 
PL/SQL procedure successfully completed.
 
SQL> select table_name, column_name, char_used
  2  from user_tab_columns
  3  where table_name in ('T1','T2', 'T3')
  4  / 
 
TABLE_NAME                     COLUMN_NAME                    C
------------------------------ ------------------------------ -
T1                             A                              C
T2                             A                              C
 
SQL> select table_name,column_name,error_flag
  2  from log_tbl;
 
TABLE_NAME      COLUMN_NAME     E
--------------- --------------- -
T1              A               P
T2              A               P
 
SQL> create table t3 (a varchar2(20) )
  2  / 
 
Table created.
 
SQL> insert into t3 (a) values ('Hello')
  2  / 
 
1 row created.
 
SQL> select table_name, column_name, char_used
  2  from user_tab_columns
  3  where table_name in ('T1','T2', 'T3');

TABLE_NAME                     COLUMN_NAME                    C
------------------------------ ------------------------------ -
T1                             A                              C
T2                             A                              C
T3                             A                              B 
 
Note the difference in the column char usage between T3 and the other tables given that T3 was created after the script was executed.
 
SQL> declare
  2    l_Err varchar2(200);
  3  begin
  4    for r in (select  atc.table_name, atc.column_name, atc.data_length
  5              from    user_tab_columns atc -- You would probably use ALL_
  6              left outer join Log_Tbl lt on (atc.Table_name   = lt.Table_Name
  7                                         and atc.Column_name = lt.Column_Name
  8                                         and lt.Error_Flag   = 'P')
  9              where   atc.data_type   = 'VARCHAR2'
 10              and     atc.char_used   = 'B'
 11              and     atc.Table_Name in ('T1', 'T2', 'T3')) loop
 12  
 13      begin 
 14        execute immediate 'alter table ' || r.table_name 
 15                                        || ' modify ' 
 16                                        || r.column_name 
 17                                        || ' varchar2('
 18                                        || r.data_length
 19                                        || ' char)';
 20      
 21        insert into Log_tbl (Table_Name, Column_Name) 
 22        values  (r.Table_Name, r.Column_Name);
 23        
 24        exception
 25          when others then
 26            l_Err := sqlerrm;
 27            insert into Log_tbl (Table_Name, Column_Name, Msg, Error_Flag) 
 28            values  (r.Table_Name, r.Column_Name, l_Err, 'F');
 29      end;
 30      
 31      commit;
 32      
 33    end loop;
 34    
 35  end;
 36  / 
 
PL/SQL procedure successfully completed.
 
SQL> select table_name, column_name, char_used
  2  from user_tab_columns
  3  where table_name in ('T1','T2', 'T3')
  4  / 
 
TABLE_NAME      COLUMN_NAME     C
--------------- --------------- -
T1              A               C
T2              A               C
T3              A               C
 
The script uses the USER_TAB_COLUMNS view. If you modify the script to use the ALL_TAB_COLUMNS or the DBA_TAB_COLUMNS view, then the script would look as follows:
 
undefine schema_name
declare
  l_Err varchar2(200);
begin
  for r in (select  atc.table_name, atc.column_name, atc.data_length
            from    all_tab_columns atc -- You would probably use ALL_
            left outer join Log_Tbl lt on (atc.Table_name   = lt.Table_Name
                                       and atc.Column_name = lt.Column_Name
                                       and lt.Error_Flag   = 'P')
            where   atc.data_type   = 'VARCHAR2'
            and     atc.char_used   = 'B'
            and     atc.Table_Name  in ('T1', 'T2', 'T3')
            and     atc.owner       = upper('&&schema_name')) loop

    begin
      execute immediate 'alter table '|| upper('&&schema_name')
                                      || '.'
                                      || r.table_name
                                      || ' modify '
                                      || r.column_name
                                      || ' varchar2('
                                      || r.data_length
                                      || ' char)';

      insert into Log_tbl (Table_Name, Column_Name)
      values  (r.Table_Name, r.Column_Name);

      exception
        when others then
          l_Err := sqlerrm;
          insert into Log_tbl (Table_Name, Column_Name, Msg, Error_Flag)
          values  (r.Table_Name, r.Column_Name, l_Err, 'F');
    end;

    commit;

  end loop;

end;
/
 
The default bhaviour, when the usage is not specified explicitly, is to set each column to BYTE. The default behaviour can be altered by setting NLS_LENGTH_SEMANTICS, for example:
 

SQL> ALTER SESSION SET NLS_LENGTH_SEMANTICS=BYTE;

Session altered.

SQL> DROP TABLE T1;

Table dropped.

SQL> DROP TABLE T2;

Table dropped.

SQL> ALTER SESSION SET NLS_LENGTH_SEMANTICS=CHAR;

Session altered.

SQL> create table t1 (a varchar2(10));

Table created.

SQL> select table_name,column_name,char_used from user_tab_columns
  2  where table_name = 'T1';

TABLE_NAME COLUMN_NAME C
---------- ----------- -
T1         A           C

SQL> ALTER SESSION SET NLS_LENGTH_SEMANTICS=BYTE;

Session altered.

SQL> create table t2 (a varchar2(10));

Table created.

SQL> select table_name,column_name,char_used from user_tab_columns
  2  where table_name IN ('T2', 'T1');

TABLE_NAME COLUMN_NAME C
---------- ----------- -
T1         A           C
T2         A           B