- They are defined by an expression. The result of evaluation of this expression becomes the value of the column.
- The values of the virtual column are not stored in the database. Rather, it’s computed at run-time when you query the data.
- You can’t update (in SET clause of update statement) the values of virtual column. These are read only values, that are computed dynamically and any attempt to modify them will result into oracle error.
The syntax for defining a virtual column is:
| column_name [datatype] [GENERATED ALWAYS] AS[expression] [VIRTUAL] | 
where the parameters within [] are optional and can be omitted. If you don’t mention the datatype, Oracle will decide it based on the result of the expression.
Excepting the above points, a virtual column, exists just like any other column of a normal table and the following points apply to it:
- Virtual columns can be used in the WHERE clause of UPDATE and DELETE statement but they can’t be modified by DML.
- Statistics can be collected on them.
- They can be used as a partition key in virtual column based partitioning.
- Indexes can be created on them. As you might have guessed, oracle would create function based indexes as we create on normal tables.
- Constraints can be created on them.
Create table with Virtual Column
For creating a virtual column, use the syntax mentioned above. Consider the following example:
| CREATETABLEEMPLOYEE(    empl_id        NUMBER,    empl_nm        VARCHAR2(50),    monthly_sal    NUMBER(10,2),    bonus          NUMBER(10,2),    total_sal      NUMBER(10,2) GENERATED ALWAYS AS(monthly_sal*12 + bonus)); | 
Here we have defined a virtual column “total_sal” whose value would be dynamically calculated using the expression provided after the “generated always as” clause. Please note that this declaration is different than using “default” clause for a normal column as you can’t refer column names with “default” clause.
Lets check the data dictionary view:
| SELECTcolumn_name, data_type, data_length, data_default, virtual_column  FROMuser_tab_cols WHEREtable_name = 'EMPLOYEE';COLUMN_NAME | DATA_TYPE | DATA_LENGTH | DATA_DEFAULT             | VIRTUAL_COLUMNEMPL_ID     | NUMBER    | 22          | null| NOEMPL_NM     | VARCHAR2  | 50          | null| NOMONTHLY_SAL | NUMBER    | 22          | null| NOBONUS       | NUMBER    | 22          | null| NOTOTAL_SAL   | NUMBER    | 22          | "MONTHLY_SAL"*12+"BONUS"| YES              | 
The value “YES” for the column “virtual_column” tells us that this is a virtual column. Another optional keyword “VIRTUAL” can also be added to make it syntactically complete.
| DROPTABLEEMPLOYEE PURGE;CREATEORREPLACEFUNCTIONget_empl_total_sal ( p_monthly_sal   NUMBER,                                                p_bonus         NUMBER)   RETURNNUMBERDETERMINISTICISBEGIN   RETURNp_monthly_sal * 12 + p_bonus;END;CREATETABLEEMPLOYEE(empl_id     NUMBER, empl_nm     VARCHAR2(50), monthly_sal NUMBER(10,2), bonus       NUMBER(10,2), total_sal   NUMBER(10,2) AS(get_empl_total_sal(monthly_sal, bonus)) VIRTUAL); | 
We have included the “VIRTUAL” clause in the table definition. Please note that instead of using an expression, I have used a deterministic function. A deterministic function, when passed certain inputs, will always return the exact same output. “DETERMINISTIC” keyword is needed in order to mark a function as a candidate to be used in a function based index.
You can also create indexes on the virtual columns. Here is an example:
| CREATEINDEXidx_total_sal ONemployee(total_sal);SELECTindex_name, index_type   FROMuser_indexes WHEREtable_name = 'EMPLOYEE';INDEX_NAME     INDEX_TYPE                 IDX_TOTAL_SAL  FUNCTION-BASED NORMAL | 
Note that even this function is used as part of table definition, you can still drop it. But this in turn will make the table inaccessible.
| DROPFUNCTIONget_empl_total_sal;SELECT* FROMemployee;*Error atline 0ORA-00904: "schema"."GET_EMPL_TOTAL_SAL": invalid identifier | 
You can alter the table with virtual column as you would modify a table with normal columns. Lets add the same column using the ALTER command:
| DROPTABLEEMPLOYEE PURGE;CREATETABLEEMPLOYEE(empl_id     NUMBER, empl_nm     VARCHAR2(50), monthly_sal NUMBER(10,2), bonus       NUMBER(10,2));ALTERTABLEEMPLOYEE ADD(total_sal AS(monthly_sal * 12 + bonus)); | 
Note that the datatype of the new column is not declared. It will be assigned a datatype based on the result of the expression (in this case, it would be NUMBER). Now let’s insert some data in the table:
| INSERTINTOemployee (empl_id, empl_nm, monthly_sal, bonus)   WITHDATA AS        (SELECT100 empl_id, 'AAA'empl_nm, 20000 monthly_sal, 3000 bonus           FROMDUAL         UNION         SELECT200, 'BBB', 12000, 2000           FROMDUAL         UNION         SELECT300, 'CCC', 32100, 1000           FROMDUAL         UNION         SELECT400, 'DDD', 24300, 5000           FROMDUAL         UNION         SELECT500, 'EEE', 12300, 8000           FROMDUAL)   SELECT*     FROMDATA;SELECT* FROMemployee;EMPL_ID | EMPL_NM | MONTHLY_SAL | BONUS | TOTAL_SAL100     | AAA     | 20000       | 3000  | 243000200     | BBB     | 12000       | 2000  | 146000300     | CCC     | 32100       | 1000  | 386200400     | DDD     | 24300       | 5000  | 296600500     | EEE     | 12300       | 8000  | 155600 | 
Here we have populated the table columns except the virtual column with some values. Upon selecting the data, we get the value for “total_sal”. Remember that this data is not actually stored in the database but evaluated dynamically. Lets try updating this value of this virtual column:
| UPDATEemployee   SETtotal_sal = 2000;ORA-54017: UPDATEoperation disallowed onvirtual columns | 
As mentioned before, the statistics can also be gathered for the virtual columns.
| EXECDBMS_STATS.GATHER_TABLE_STATS(user, 'EMPLOYEE');SELECTcolumn_name, num_distinct,        display_raw (low_value, data_type)  low_value,        display_raw (high_value, data_type) high_value  FROMdba_tab_cols WHEREtable_name = 'EMPLOYEE';COLUMN_NAME | NUM_DISTINCT | LOW_VALUE | HIGH_VALUETOTAL_SAL   | 5            | 146000    | 386200BONUS       | 5            | 1000      | 8000MONTHLY_SAL | 5            | 12000     | 32100EMPL_NM     | 5            | AAA       | EEEEMPL_ID     | 5            | 100       | 500 | 
Limitations on Virtual Columns
**The query above uses a wonderful function “display_raw” by “Greg Rahn” to display the high/low values. Please check the references at the last to see it’s definition.
- A virtual column can only be of scalar datatype or XMLDATATYE. It can’t be a user defined type, LOB or RAW.
- All columns mentioned as part of the virtual column expression should belong to the same table.
- No DMLs are allowed on the virtual columns.
- The virtual column expression can’t reference any other virtual column.
- Virtual columns can only be created on ordinary tables. They can’t be created on index-organized, external, object, cluster or temporary tables.
- If a deterministic function is used as virtual column expression, that virtual column can’t be used as a partitioning key for virtual column-based partitioning.
Virtual Column-Based Partitioning
Prior releases of Oracle only allowed a table to be partitioned based on a physical column. Oracle 11g, with the addition of virtual columns, now allows a partition key based on an expression, using one or more existing columns of the table. A virtual column can now be used as a partitioning key. Lets partition our table based on the virtual column “total_sal”:
| DROPTABLEEMPLOYEE PURGE;CREATETABLEemployee(empl_id     NUMBER, empl_nm     VARCHAR2(50), monthly_sal NUMBER(10,2), bonus       NUMBER(10,2), total_sal   NUMBER(10,2) AS(monthly_sal*12 + bonus))PARTITION BYRANGE (total_sal)    (PARTITION sal_200000 VALUESLESS THAN (200000),     PARTITION sal_400000 VALUESLESS THAN (400000),     PARTITION sal_600000 VALUESLESS THAN (600000),     PARTITION sal_800000 VALUESLESS THAN (800000),     PARTITION sal_default VALUESLESS THAN (MAXVALUE));INSERTINTOemployee (empl_id, empl_nm, monthly_sal, bonus)   WITHDATA AS        (SELECT100 empl_id, 'AAA'empl_nm, 20000 monthly_sal, 3000 bonus           FROMDUAL         UNION         SELECT200, 'BBB', 12000, 2000           FROMDUAL         UNION         SELECT300, 'CCC', 32100, 1000           FROMDUAL         UNION         SELECT400, 'DDD', 24300, 5000           FROMDUAL         UNION         SELECT500, 'EEE', 12300, 8000           FROMDUAL)   SELECT*     FROMDATA;EXECDBMS_STATS.GATHER_TABLE_STATS(user,'EMPLOYEE',granularity => 'PARTITION');SELECTtable_name, partition_name, num_rows    FROMuser_tab_partitions   WHEREtable_name = 'EMPLOYEE'ORDERBYpartition_name;TABLE_NAME | PARTITION_NAME | NUM_ROWSEMPLOYEE   | SAL_200000     | 2EMPLOYEE   | SAL_400000     | 3EMPLOYEE   | SAL_600000     | 0EMPLOYEE   | SAL_800000     | 0EMPLOYEE   | SAL_DEFAULT    | 0 | 
So far, everything looks fine, lets now try to update monthly salary of one employee and in turn the value of total_sal.
| UPDATEemployee   SETmonthly_sal = 30000 WHEREempl_id = 500;ORA-14402: updating partition keycolumnwould cause a partition change | 
What happened? The reason is simple, updating the “monthly_sal” would result into change in “total_sal” of the employee and thus a partition change is required. This can be handled by enabling the row movement in the current definition of the table.
| ALTERTABLEemployee ENABLE ROW MOVEMENT;UPDATEemployee   SETmonthly_sal = 80000 WHEREempl_id = 500;1 row updated. | 
The update works fine. As mentioned before, a deterministic function can’t be used as virtual column expression which is to be used as a partitioning key. It has to be an expression defined on the columns of the table as done in the previous example. The following syntax will result in oracle error:
| CREATETABLEemployee_new(empl_id     NUMBER, empl_nm     VARCHAR2(50), monthly_sal NUMBER(10,2), bonus       NUMBER(10,2), total_sal   NUMBER(10,2) AS(get_empl_total_sal(monthly_sal, bonus)))PARTITION BYRANGE (total_sal)    (PARTITION sal_200000 VALUESLESS THAN (200000),     PARTITION sal_400000 VALUESLESS THAN (400000),     PARTITION sal_600000 VALUESLESS THAN (600000),     PARTITION sal_800000 VALUESLESS THAN (800000),     PARTITION sal_default VALUESLESS THAN (MAXVALUE));ORA-54021: Cannot use PL/SQL expressions inpartitioning orsubpartitioning columns | 
 
 
No comments:
Post a Comment