From 11g onwards we can create a virtual column. A virtual column is not stored on disk.
Rather, oracle derives the values in a virtual column demand by computing the function
or expressions defined on the respective virtual column.
Syntax for virtual column definition is:
column_name [data_type] [generated always] as (expression) [virtual]
If the datatype is omitted, it is determined on the result of the expression. The
GENERATED ALWAYS and VIRTUAL keywords are provided for clarity only and optional
In the below example i am creating a virtual which is an expression as “n1*n2”
drop table t1 purge; create table t1( n1 number , n2 number , v1 varchar2(30) , d1 date , vc1 number generated always as (n1*n2) virtual ) ;
Now lets try to insert some data into this table using following:
insert into t1 select rownum n1 , mod(rownum, 100) n2 , lpad(rownum, '30', '*') v1 , sysdate + rownum /24/60 d1 , rownum * mod(rownum, 100) vc1 from dual where rownum <= 1e5 ; insert into t1 * ERROR at line 1: ORA-54013: INSERT operation disallowed on virtual columns
Here we get the error as we can not insert a value explicitly into a virtual column. So
for inserting data into a table having a virtual we have to give the column names explicitly
excluding the virtual columns.
insert into t1 ( n1 , n2 , v1 , d1 ) select rownum n1 , mod(rownum, 100) n2 , lpad(rownum, '30', '*') v1 , sysdate + rownum /24/60 from all_objects where rownum <= 1e5 ; commit;
Querying the data from this table we can see the value of our virtual column:
select * from t1 where rownum <= 2 ; N1 N2 V1 D1 VC1 ---------- ---------- ------------------------------ --------- ---------- 1 1 *****************************1 02-JUL-14 1 2 2 *****************************2 02-JUL-14 4
We can also check the expression used for generating our virtual column in DATA_DEFAULT column
of [dba|all|user]_tab_columns
col data_default format a40 select column_name , data_default from user_tab_columns where table_name = 'T1' ; COLUMN_NAME DATA_DEFAULT ------------------------------ ---------------------------------------- N1 N2 V1 D1 VC1 "N1"*"N2"
Now to see that actually oracle is storing the data for our virtual column, i will be
creating two tables as following:
pro ***************************************************** pro creating and populating table without virtual columns pro ***************************************************** drop table t1 purge; create table t1( n1 number , n2 number , v1 varchar2(30) , d1 date ) ; insert into t1 ( n1 , n2 , v1 , d1 ) with d as( select rownum n1 from all_objects where rownum <= 1e3 ) select rownum n1 , mod(rownum, 100) n2 , lpad(rownum, '30', '*') v1 , sysdate + rownum /24/60 from d d1 , d d2 where rownum <= 1e5 ; pro ***************************************************** pro creating and populating table with virtual columns pro ***************************************************** drop table t2 purge; create table t2( n1 number , n2 number , v1 varchar2(30) , d1 date , vc1 number generated always as (n1*n2) virtual , vc2 number as (n1+n2) , vc3 number as (n1-n2) ) ; insert into t2 ( n1 , n2 , v1 , d1 ) with d as( select rownum n1 from all_objects where rownum <= 1e3 ) select rownum n1 , mod(rownum, 100) n2 , lpad(rownum, '30', '*') v1 , sysdate + rownum /24/60 from d d1 , d d2 where rownum <= 1e5 ; commit;
From below we can check which are the data defaults for our virtual columns
select table_name , column_name , data_default from user_tab_columns where table_name in ('T1', 'T2') ; TABLE_NAME COLUMN_NAME DATA_DEFAULT ------------------------------ ------------------------------ ------------ T1 N1 T1 N2 T1 V1 T1 D1 T2 N1 T2 N2 T2 V1 T2 D1 T2 VC1 "N1"*"N2" T2 VC2 "N1"+"N2" T2 VC3 "N1"-"N2"
We can also use the view user_tab_cols to see the virtual column information as
col column_name format a10 col table_name format a11 select table_name , column_name , hidden_column , virtual_column , segment_column_id , internal_column_id from user_tab_cols where table_name in ('T1', 'T2') ; TABLE_NAME COLUMN_NAM HID VIR SEGMENT_COLUMN_ID INTERNAL_COLUMN_ID ----------- ---------- --- --- ----------------- ------------------ T1 N1 NO NO 1 1 T1 N2 NO NO 2 2 T1 V1 NO NO 3 3 T1 D1 NO NO 4 4 T2 N1 NO NO 1 1 T2 N2 NO NO 2 2 T2 V1 NO NO 3 3 T2 D1 NO NO 4 4 T2 VC1 NO YES 5 T2 VC2 NO YES 6 T2 VC3 NO YES 7
Now lets check the size of the physical storage of these two tables
col segment_name format a12 select segment_name , bytes , blocks , extents from user_segments where segment_name in ('T1', 'T2') ; SEGMENT_NAME BYTES BLOCKS EXTENTS ------------ ---------- ---------- ---------- T1 6291456 768 21 T2 6291456 768 21
Here oracle has used the same physical storage for both tables although T2 has three
more columns than T1 which are defined as VIRTUAL.
We can create indexes on a virtual column which are similar to function based indexes and
they will be using the disk space to store its data.
drop index t2_vc1; create index t2_vc1 on t2(vc1); begin dbms_stats.gather_table_stats( user , 't2' ); end; / select segment_name , bytes , blocks , extents from user_segments where segment_name = 'T2_VC1' ; SEGMENT_NAME BYTES BLOCKS EXTENTS ------------ ---------- ---------- ---------- T2_VC1 2097152 256 17
Now lets query the data from this table and check whether the index is used or not
select * from t2 where vc1 = 4 ; select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------- SQL_ID g8kc5x3j8wgzg, child number 1 ------------------------------------- select * from t2 where vc1 = 4 Plan hash value: 1966060445 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 62 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T2_VC1 | 1 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("VC1"=4)
From above we can see that oracle optimizer has used our index to retrieve the data. Now
lets try one another having the same expression as defined in the virtual and see whether it
can use the index defined on virtual column
select * from t2 where n1*n2 = 4 ; select * from table(dbms_xplan.display_cursor()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------- SQL_ID fxc0fdrmp7ugy, child number 0 ------------------------------------- select * from t2 where n1*n2 = 4 Plan hash value: 1966060445 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | | 1 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 62 | 3 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T2_VC1 | 1 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("T2"."VC1"=4)
Here also oracle optimizer has choosen the index internally for retrieving our data as the
indexes on virtual columns are similar to function based indexes.