Oracle change column datatype from varray - arrays

In Oracle database I have two schemas with two tables that mirror each other, except for one column which in both tables is varray of numbers, but in schema A it's A.VARRAY, and in schema B it's B.VARRAY.
As a result I'm unable to migrate data between tables, since they have inconsistent datatypes.
Is there any way to change column datatype from A.varray to B.varray without losing data?

Just use cast(col as B.VARRAYTYPE):
SQL> CREATE OR REPLACE TYPE V_TYPE_1 AS VARRAY(5000) OF NUMBER(1);
2 /
Type created.
SQL> CREATE OR REPLACE TYPE V_TYPE_2 AS VARRAY(5000) OF NUMBER(1);
2 /
Type created.
SQL> create table t1 (a V_TYPE_1);
Table created.
SQL> insert into t1 values(v_type_1(1,2,3,4,5));
1 row created.
SQL> create table t2 (a V_TYPE_2);
Table created.
SQL> insert into t2 select cast(a as v_type_2) from t1;
1 row created.
SQL> select * from t2;
A
-------------------------
V_TYPE_2(1, 2, 3, 4, 5)
1 row selected.

CAST it is, I agree with Sayan. Though, as there are two users involved, there are some in-between steps required - grant execute on type being most important, I'd say. Here's an example.
My users are scott and mike. Each of them has the same table description. scott is supposed to insert rows into mike's table.
Connected as scott:
SQL> show user
USER is "SCOTT"
SQL> create or replace type v_type as varray(5000) of number(1);
2 /
Type created.
SQL> create table test (id number, a v_type);
Table created.
SQL> insert into test(id, a) values (1, v_type(1));
1 row created.
SQL>
Connected as mike: uses the same type as scott:
SQL> show user
USER is "MIKE"
SQL> create or replace type v_type as varray(5000) of number(1);
2 /
Type created.
SQL> create table test (id number, a v_type);
Table created.
SQL> grant insert on test to scott;
Grant succeeded.
SQL>
Connected as scott, trying to insert row(s) into mike's table:
SQL> show user
USER is "SCOTT"
SQL> insert into mike.test (id, a) select id, a from test;
insert into mike.test (id, a) select id, a from test
*
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected MIKE.V_TYPE got SCOTT.V_TYPE
Let's try CAST:
SQL> insert into mike.test (id, a) select id, cast(a as mike.v_type) from test;
insert into mike.test (id, a) select id, cast(a as mike.v_type) from test
*
ERROR at line 1:
ORA-00904: : invalid identifier
SQL>
In order to make it work, mike has to grant execute on their type to scott:
SQL> show user
USER is "MIKE"
SQL> grant execute on v_type to scott;
Grant succeeded.
SQL>
Finally, it works:
SQL> show user
USER is "SCOTT"
SQL> insert into mike.test (id, a) select id, cast(a as mike.v_type) from test;
1 row created.
SQL>

Would you be willing to add a column to one of the tables, populate the new column with values from the other column, say through an update statement, then drop old column?
T1(c1 V_TYPE_1);
T2(c1 V_TYPE_2);
(1) Add column to T2:
T2(c1 V_TYPE_2, c2 V_TYPE_1);
(2) Update T2 so c1 and c2 are the same values for every row.
(3) Drop old column from T2:
T2(c2 V_TYPE_1);
This solution would be a little easier if the table was not active. If the table was active you will want to add a trigger so that the two columns are in sync.

Related

ORA-04089: cannot create triggers on objects owned by SYS

I want to create trigger but i get this error.ORA-04089: cannot create triggers on objects owned by SYS
04089. 00000 - "cannot create triggers on objects owned by SYS"
How can i solve this problem?.Why cannot I create triggers on objects owned by SYS?
create table superhero(name varchar2(30));
create or replace trigger tg_sup
before insert on superhero
for each row disable
declare
v_user varchar2(30);
begin
select user into v_user from dual;
DBMS_OUTPUT.PUT_line('you just inserted a line by '||v_user);
end;
As you were told: you can't do it in SYS schema. It, just like SYSTEM, is special, it owns the database and should only be used for maintenance purposes. Create your own user to brush SQL skills; for example, I have the scott user:
SQL> create table superhero
2 (name varchar2(30));
Table created.
SQL> create or replace trigger tg_sup
2 before insert on superhero
3 for each row
4 disable
5 declare
6 v_user varchar2(30);
7 begin
8 select user into v_user from dual;
9 DBMS_OUTPUT.PUT_line('you just inserted a line by '||v_user);
10 end;
11 /
Trigger created.
Let's try it:
SQL> set serveroutput on
SQL> insert into superhero(name) values ('Littlefoot');
1 row created.
Good; but, there's no message (from the trigger). How come? Because you chose to create it in disabled state (why did you do that?):
SQL> select status from user_Triggers where trigger_name = 'TG_SUP';
STATUS
--------
DISABLED
If we enable it:
SQL> alter trigger tg_sup enable;
Trigger altered.
SQL> insert into superhero(name) values ('Bigfoot');
you just inserted a line by SCOTT --> message!
1 row created.
SQL>
OK, message is being displayed.

Practices regarding Nullable Foreign Keys & Alternatives to it

I am currently working on designing and implementing a database.
We are running a conference, and there are two ways for attendees to register to the conference. The first is that the register and pay online - quite straightforward. The second is that they physically attend a "registration day" where they pay cash, and are added by one of our many admins. In this case, we would like to keep track of which admin added the attendee - for internal purposes.
We believe that it is best to store all attendees in the same table, and we would need a foreign key to track the admin that each attendee was added by. But for attendees registering online and paying themselves, we simply want to indicate that they were not added by any admin (so the foreign key would be Null).
Is the proposed idea of nullable foreign keys bad practice? Are there any benefits to storing attendees in different tables, where attendees registering online do not have a column for addedBy at all and cash-paying attendees have a non-nullable addedBy column?
I wouldn't store attendees in two (or more?) separate tables. They belong into the same table, and there should be a column which will tell you how someone got registered. Will it be "admin name" (or ID), checkbox, radio button item, whatever - just keep them in the same table.
Why? Because currently you think of two tables. It means that all code you write will have to be "duplicated" for those two tables - all inserts, updates, selects ... everything.
Now imagine that someone gets a super idea of the 3rd way of registration (snail mail, for example). What will you do then? Create the 3rd table, and triple code you wrote? That would be a really bad practice.
There's nothing "wrong" if a foreign key column doesn't contain any value, if you decide to do so - just document it. For example (Oracle syntax; would be similar in any other database):
SQL> create table admins
2 (id_admin number constraint pk_adm primary key,
3 name varchar2(20) not null
4 );
Table created.
SQL> create table attendees
2 (id_attendee number constraint pk_att primary key,
3 name varchar2(20) not null,
4 id_admin number constraint fk_att_adm references admins (id_admin)
5 );
Table created.
A few sample rows:
SQL> -- admin
SQL> insert into admins (id_admin, name)
2 select 1, 'Little' from dual;
1 row created.
SQL> -- attendees
SQL> -- registered online
SQL> insert into attendees (id_attendee, name) values (1, 'Mike');
1 row created.
SQL> -- registered on site
SQL> insert into attendees (id_attendee, name, id_admin) values (2, 'Scott', 1);
1 row created.
What do we know about them? It has to be outer join because - if it's not - you won't get info about people registered online:
SQL> select t.id_attendee, t.name attendee, a.name registered_by
2 from attendees t left join admins a on a.id_admin = t.id_admin;
ID_ATTENDEE ATTENDEE REGISTERED_BY
----------- -------------------- --------------------
1 Mike
2 Scott Little
SQL>
If you don't want to allow NULL values for a foreign key column, then a simple (and not bad at all) option is to create a "dummy" admin (let's call it "Online"):
SQL> insert into admins (id_admin, name) values (0, 'Online');
1 row created.
Set it for all users registered online (their id_admin column value is NULL):
SQL> update attendees set id_admin = 0 where id_admin is null;
1 row updated.
Disallow NULL values for a foreign key column:
SQL> alter table attendees modify id_admin not null;
Table altered.
Now you can use inner (instead of outer) join:
SQL> select t.id_attendee, t.name attendee, a.name registered_by
2 from attendees t join admins a on a.id_admin = t.id_admin;
ID_ATTENDEE ATTENDEE REGISTERED_BY
----------- -------------------- --------------------
1 Mike Online
2 Scott Little
SQL>
Snail mail? No problem, you don't have to modify any code you wrote so far:
SQL> insert into admins (id_admin, name) values (3, 'Snail mail');
1 row created.
SQL> insert into attendees (id_attendee, name, id_admin) values (3, 'King', 3);
1 row created.
SQL> select t.id_attendee, t.name attendee, a.name registered_by
2 from attendees t join admins a on a.id_admin = t.id_admin;
ID_ATTENDEE ATTENDEE REGISTERED_BY
----------- -------------------- --------------------
1 Mike Online
2 Scott Little
3 King Snail mail
SQL>

Will deleting column data reduce database size?

I have a table with several columns, one of which is a CLOB containing large XML project file data. That one column accounts for 99% of the size of that table, and the table has grown to several Gb. Our DBA needs to migrate the database and wants to reduce the size as much as possible beforehand. We can't lose any rows from that table but we would be safe in clearing out the data in that particular CLOB column. Would updating the table to remove that data reduce the overall size (I assume if it did it would be in conjunction with some administrative re-indexing action or something)?
If you don't need any CLOB data, drop that column:
SQL> create table test
2 (id number,
3 cclob clob);
Table created.
SQL> insert into test (id, cclob) values (1, 'Littlefoot');
1 row created.
SQL> alter table test drop column cclob;
Table altered.
SQL>
Alternatively, create a new table with primary key column and CLOB column:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ------------------
DEPTNO NUMBER(2)
DNAME VARCHAR2(14)
LOC VARCHAR2(13)
CCLOB CLOB
SQL> create table new_test as select deptno, cclob from test;
Table created.
SQL> alter table test drop column cclob;
Table altered.
SQL>
Now you can move the new, lightweight table to another server. If you need some of CLOB data, you can update the new table (on a new server) as
SQL> update test_on_new_server a set
2 a.cclob = (select b.cclob
3 from test_from_old_server b
4 where b.deptno = a.deptno
5 )
6 where a.loc = 'NEW YORK';

How write Alter Table and add new columns?

I have a table that has 3 columns A,B,C which has rows also. Column A is the primary key.
Now as per new requirement I need to add new column D, E and F.
Also i need to remove the previous primary key from column A and add a new primary key for column D.
Column E and F is NULL.
Please help me to create alter table statement.
What you require is a multi-step process. Adding the columns, dropping the existing primary key constraint and finally adding a new one.
The most difficult thing here is adding column D. Because you want it to be the new primary key it will have to be NOT NULL. If your table has existing data you will need to handle this error:
SQL> alter table your_table
2 add ( d number not null
3 , e date
4 , f number )
5 /
alter table your_table
*
ERROR at line 1:
ORA-01758: table must be empty to add mandatory (NOT NULL) column
SQL>
So, step 1 is add the new columns with D optional; then populate it with whatever key values:
SQL> alter table your_table
2 add ( d number
3 , e date
4 , f number )
5 /
Table altered.
SQL> update your_table
2 set d = rownum
3 /
1 row updated.
SQL>
Now we can make column D mandatory:
SQL> alter table your_table
2 modify d not null
3 /
Table altered.
SQL>
Finally, we can change the primary key column from A to D:
SQL> alter table your_table
2 drop primary key
3 /
Table altered.
SQL> alter table your_table
2 add constraint yt_pk primary key (d)
3 /
Table altered.
SQL>
For some alterations we want to add a column with a default value. In this scenario it is possible to do so in one step:
alter table your_table
add new_col varchar2(1) default 'N' not null;
In later versions of Oracle this is actually an extremely efficient of populating the new column with the same value, considerably faster than the multi-step approach outlined above.
In case it's not clear the above syntax is Oracle. I expect SQL Server will be something similar.

In which cases will Oracle create indexes automatically?

As far as I know (this page) Oracle automatically creates an index for each UNIQUE or PRIMARY KEY declaration. Is this a complete list of cases when indexes are created automatically in Oracle?
I'll try to consolidate given answers and make it community wiki.
So indexes are automatically created by Oracle for such cases:
APC: For primary key and unique key unless such indexes already exist.
APC: For LOB storage and XMLType.
Gary: For table with a nested table.
Jim Hudson: For materialized view.
Firstly, Oracle does not always create an index when we create a primary or unique key. If there is already an index on that column it will use it instead...
SQL> create table t23 (id number not null)
2 /
Table created.
SQL> create index my_manual_idx on t23 ( id )
2 /
Index created.
SQL> select index_name from user_indexes
2 where table_name = 'T23'
3 /
INDEX_NAME
------------------------------
MY_MANUAL_IDX
SQL>
... note that MY_MANUAL_IDX is not a unique index; it doesn't matter ...
SQL> alter table t23
2 add constraint t23_pk primary key (id) using index
3 /
Table altered.
SQL> select index_name from user_indexes
2 where table_name = 'T23'
3 /
INDEX_NAME
------------------------------
MY_MANUAL_IDX
SQL> drop index my_manual_idx
2 /
drop index my_manual_idx
*
ERROR at line 1:
ORA-02429: cannot drop index used for enforcement of unique/primary key
SQL>
There is another case when Oracle will automatically create an index: LOB storage....
SQL> alter table t23
2 add txt clob
3 lob (txt) store as basicfile t23_txt (tablespace users)
4 /
Table altered.
SQL> select index_name from user_indexes
2 where table_name = 'T23'
3 /
INDEX_NAME
------------------------------
MY_MANUAL_IDX
SYS_IL0000556081C00002$$
SQL>
edit
The database treats XMLType same as other LOBs...
SQL> alter table t23
2 add xmldoc xmltype
3 /
Table altered.
SQL> select index_name from user_indexes
2 where table_name = 'T23'
3 /
INDEX_NAME
------------------------------
MY_MANUAL_IDX
SYS_IL0000556081C00002$$
SYS_IL0000556081C00004$$
SQL>
No, we're getting closer but that's not quite a complete list yet.
There will also be an index automatically created when you create materialized view since Oracle needs to be able to quickly identify the rows when doing a fast refresh. For rowid based materialized views, it uses I_SNAP$_tablename. For primary key materialized views, it uses the original PK name, modified as necessary to make it unique.
create materialized view testmv
refresh force with rowid
as select * from dual;
select index_name from user_indexes where table_name = 'TESTMV';
Index Name
--------------
I_SNAP$_TESTMV
And another one, if you create a table with a nested table you get an index created automatically. Object based storage in general can do this as there can be hidden tables created.
I think schema-based XMLTypes will also do it.
Yes, that's the complete list. Oracle automatically creates an index for each UNIQUE or PRIMARY KEY declaration.

Resources