In the previous note about a problem dropping virtual columns the “guilty party” that made it impossible to drop any columns was based on a complex data type owned by the MDSYS (Spatial) schema. This note demonstrates the same problem with a very simple example created from scratch in an ordinary user schema.
rem
rem Script: object_virtual_col.sql
rem Author: Jonathan Lewis
rem Dated: Mar 2022
rem
rem Last tested
rem 19.11.0.0
rem
create type point_type as object(x_coord number, y_coord number);
/
create or replace function my_point(inpoint in point_type)
return point_type
deterministic as
begin
return inpoint;
end;
/
show errors
create table t1 (id, n1, p1, n2, v1, padding)
as
with generator as (
select
rownum id
from dual
connect by
level <= 1e4 -- > comment to avoid WordPress format issue
)
select
rownum id,
rownum n1,
point_type(rownum, rownum) p1,
rownum n2,
lpad(rownum,10,'0') v1,
lpad('x',100,'x') padding
from
generator v1
where
rownum <= 100 -- > comment to avoid WordPress format issue
;
begin
dbms_stats.gather_table_stats(
ownname => null,
tabname => 'T1',
method_opt => 'for all columns size 1'
);
end;
/
alter table t1 add constraint t1_pk primary key(id);
So I’ve declared a type “point” which is an object with two attributes of type number, and I’ve created a function that takes a point as its input parameter and returns a point. Then I’ve created a table which includes a column of type point.
Let’s start with a little reminder of what a pain it is to use even simple object types correctly. What’s going to happen with the following three SQL statements:
select p1.x_coord from t1 where rownum <= 4;
select t1.p1.x_coord from t1 where rownum <= 4;
select t1.p1.x_coord from t1 t1 where rownum <= 4;
The first two will fail – the first one shouldn’t be too surprising, the second does seem a little unreasonable:
ORA-00904: "P1"."X_COORD": invalid identifier
ORA-00904: "T1"."P1"."X_COORD": invalid identifier
So let’s try adding some virtual columns to pick out the X value:
alter table t1 add x_val generated always as (p1.x_coord) virtual;
alter table t1 add c_val generated always as (cast(p1.x_coord as number)) virtual;
The first call will fail (ORA-54016: Invalid column expression was specified) but the second will succeed. What if we try to hide out point column behind a call to our function:
alter table t1 add fp_val generated always as (my_point(p1)) virtual;
alter table t1 add fx_val generated always as (my_point(p1).x_coord) virtual;
Again the first call will fail (ORA-54004: resultant data type of virtual column is not supported) but that’s a documented restriction – a user-defined type may not be used as the type of a virtual column and I wasn’t at that point trying to return just the one attribute.
The second call, however, will succeed. So I can’t create a virtual column p1.x_coord, but I can create a virtual column my_point(p1).x_coord.
We now have two virtual columns that should return the required values, so that’s do a quick check with a couple of simple queries – cut and paste:
SQL> select fx_val "my_point(p1).x_coord" from t1 where rownum <= 4;
my_point(p1).x_coord
--------------------
1
2
3
4
4 rows selected.
SQL> select c_val "cast(p1.x_coord as -" from t1 where rownum <= 4;
cast(p1.x_coord as -
--------------------
1
2
3
4
4 rows selected.
Finally we’ll finish off by demonstrating that I’ve just created a problem that no-one will notice until long after I’ve left the site (maybe):
SQL> alter table t1 drop column n1;
alter table t1 drop column n1
*
ERROR at line 1:
ORA-00904: "TEST_USER"."MY_POINT": invalid identifier
After creating (and using successfully) the virtual column that calls my function, I can’t drop any of the columns in the table.
Summary
The manuals have a stated restriction for virtual columns that they cannot be a user-defined type, and this restriction seems to carry forward to an attribute of a user-defined type unless the attribute has been cast() to a base type.
The same restriction seems to apply to functions returning a user-defined type, but not to the individual attributes of the returned value – it is not necessary to cast() them to a base type. However, if you (accidentally) take advantage of this relaxation of the restriction you will be unable to drop any columns from the table in the future.