Modify a nested table in Oracle - oracle

I have the below Nested table created :
create or replace TYPE access_t AS OBJECT (
AccessID VARCHAR2(50),
Eligibility char(1)
);
/
create or replace TYPE Access_tab IS TABLE OF access_t;
/
create or replace TYPE add_t AS OBJECT (
city VARCHAR2(100),
state VARCHAR2(100),
zip VARCHAR2(10),
APOINTSARRAY Access_tab )
;
/
create or replace TYPE add_tab IS TABLE OF add_t;
/
CREATE TABLE RQST_STATUS
( RQST_ID NUMBER,
ADDRESS add_tab
)
NESTED TABLE ADDRESS STORE AS RQST_STATUS_ADDRESS
( NESTED TABLE APOINTSARRAY STORE AS RQST_STATUS_AP)
;
If i need to change ADDRESS type to new_add_tab with some additional columns instead of add_tab , Can i just use ALTER TABLE .. MODIFY .. command ?
I am getting ORA-00922 or ORA-22913 errors . I cannot change the type directly because it is used somewhere else too. Also, the table is already loaded with data.
Please suggest.

You can do that but you have to alter the TYPE not the TABLE.
Check the documentation ALTER TYPE Statement: alter_method_spec
Most important is the CASCADE key word.
Examples:
ALTER TYPE access_t ADD ATTRIBUTE NEW_Eligibility INTEGER CASCADE;
ALTER TYPE access_t DROP ATTRIBUTE Eligibility CASCADE;
ALTER TYPE access_t MODIFY ATTRIBUTE AccessID VARCHAR2(100) CASCADE;

Here is a step-by-step description of my suggestion. It might not be the most elegant, but I think that it would be best for you to have something you can fully understand (as opposed to an obscure trick).
Also, and since I don't really know what kind of changes you need to do for the internal table, I'm leaving the maximal flexibility for you to do any change you may wish to do.
Let's call your table T1 that contains a columns C_T which is your internal table.
The internal table contains columns C_1, C-2 and C_3, and you want the new structure for the record to be D_1, D_2, D_3, D_4 and D_5, where the mapping is:
C_1 -> D_5,
C_2 -> D_1,
C_3 -> D_2,
{new} -> D_3,
{new} -> D_4.
Create a tempo table TEMPO_T with a column SOURCE_ROWID (varchar2(64)) and the new columns D_1,..., D5.
Write a small anonymous block having a cursor that selects the ROWID of each row of table T1 and all the records within the internal table in column C_T (order by ROWID). The result would look like (this is just an example of course):
ROWID C_1 C_2 C_2
wwereeedffff 1 a ww
wwereeedffff 2 b xx
wwereeedffff 7 l yy
ertrtrrrtrrr 5 d PP
ertrtrrrtrrr 99 h mm
...
[Note: The use of ROWID is under the assumption that you don't have a column that can serve as a unique identifier for each row in table T1; if there is such column - one defined as UNIQUE INDEX - you can use that field instead]
Having this query ready, convert it into an INSERT into the temporary table TEMPO_T along with whatever values you need to store for columns D_3 and D_4.
Now, you have a backup of the original contents of column C_T and hence can delete the column.
Now, you can update the type that defines the structure of column C_T to its new form (i.e. D_1,...,D_5) and alter table T1 by adding a column whose type is the updated one.
Finally, you can insert the contents of column C_T with that stored in the temporary table (since you already have this, I assume that you know how to implement it - inserting a table within a cell column of the outer table).
That's it.
Needless to say, I would make a backup of your data before engaging into this.
Hope this description is detailed enough to enable you to complete the task at hand.

Related

adding data from two different tables using a trigger

I created three tables A (id, name, date, realnumber, integer), B (id, name, date, realnumber, integer), and C which is identical to table A. It only has two more columns called integerB and sequence s. I want to create a trigger which would fire after insert on table B for each row input so that it saves the referenced row of Table A and adds integer from input row of table B in column integerB of table C. If the row already exists in Table C only integerB should be added. When it comes to sequence s, next value is added with first insert of row of table A.
Simple explanation: Table C is a copy of table A with two additional columns: integerB and sequence. The point of the trigger is to add new rows from table A without repetition, integerB from table B(integer in table B) and sequence should start with 1 and increment by 1. If the row in table A is repeated then only integerB should be updated.
I did not work with triggers that much, so I am not sure how to solve the problem when I have to insert data from multiple tables. Here is my trigger.
CREATE OR REPLACE TRIGGER trig1
AFTER INSERT ON B
FOR EACH ROW
INSERT INTO C (integerB) VALUES (NEW.integer);
INSERT INTO C (id, name, date, realnumber)
SELECT a.id, a.name, a.date, a.realnumber FROM A a;
END;
/
First off you really need to use better column and table names as a lot of these are reserved words... This makes everything far more complicated than it needs to be.
It isn't entirely clear what you want to do but it seems that if someone was to insert a record with ID = 1 into B then you want to get the values from A for ID = 1 and store them in C along with the integer value inserted into B
In which case you want to use an MERGE statement (UPSERT) in your trigger and something like
CREATE OR REPLACE TRIGGER T1
AFTER INSERT ON B
FOR EACH ROW
BEGIN
MERGE INTO C C
USING (SELECT * FROM A WHERE ID = :NEW.ID) A
ON (C.ID = :NEW.ID)
WHEN MATCHED THEN UPDATE
SET C.INTEGERB = :NEW.INTEGER,
C.SEQUENCE = C.SEQUENCE + 1
WHEN NOT MATCHED THEN
INSERT (ID, NAME, DATE, REALNUMBER, INTEGER, INTEGERB, SEQUENCE)
VALUES (A.ID, A.NAME. A.DATE, A.REALNUMBER, A.INTEGER, :NEW.INTEGER, 0);
END;
/
For sequence this has been set to 0 when a new record is inserted into C and then incremented each time integerB is updated. I am not sure if this is waht you want or not.
You should be able to tweak this to match the exact joins and logic you need.
Tip
Get your SQL statement working with literal values first and then translate it into your trigger. It will be much easier if you can get something working manually first before you attempt to make things more complicated

Is there a way similar check constraint to help me to know if there is a duplicate column

table 1
ID - name - main_number - random1 - random2
1* -aaaa-blalablabla*- *** - *
2 -vvvv-blublubluuu*- *** - *
3 -aaaa-blalablabla*- *** - **
ID , name and main number are primary key
My problem that I have noticed coulmn name and main number has duplicate values, i dont want to ADD ANY OTHER DUPLICATE VALUES ( I should keep the old duplicat because in my real table there are a lot of duplicated data and its hard to remove them )
what I want when I TRY ( BEFORE TO COMMIT) to know that this name I am trying to insert is duplicate.
I can do that with in a procedure or triger, but i have heard constraint checking is simpler and easier(if there a simpler way then procedure or triger ill be glad to learn it)
CONSTRAINT check_name
CHECK (name = (A_name))
can the constaraint have more then 1 column in such way?
CONSTRAINT check_name
CHECK (name = (A_name) , main_number=( A_number))
can I a write a constaraint in such way?
CONSTRAINT check_name
CHECK (name = ( select case where there is an column has the same value of column name))
So my question : Is there a way simelar to check constraint to help me to know if there is a duplicate column or I have to use a trigger ?
Since your database is Oracle you could also use NOVALIDATE constraints. Meaning: "doesn't matter how the data is, just validate from now on".
create table tb1
(field1 number);
insert into tb1 values (1);
insert into tb1 values (1);
insert into tb1 values (1);
insert into tb1 values (2);
insert into tb1 values (2);
commit;
-- There should be an non-unique index first
create index idx_t1 on tb1 (field1);
alter table tb1 add constraint pk_t1 primary key(field1) novalidate;
-- If you try to insert another 1 or 2 you would get an error
insert into tb1 values (1);
Yes, you can use constraints on many columns.
But in this case constraint is not applicable, because all table rows must satisfy constraints. Use a trigger.
Constraints cannot contain subqueries.
Alternatively use unique index, that will enforce unique constraint
create unique index index1 on table1
(case when ID <= XXX then null else ID end,
case when ID <= XXX then null else name end);
Replace 'XXX' with your current max(ID).
I assume that you want to prevent duplicate records as defined by the combination of name and main_number.
Then the way to go is to cleanup your database, and create a unique index:
create unique index <index_name> on <table> (name, main_number)
This both checks, and speed's it up.
In theory, if you really wanted to keep the old duplicate records, you could get along by using a trigger, but then you will have a hard time trying to get sense out of this data.
Update
If you used the trigger, you would end up with two partitions of data in one table - one is checked, the other is not. So all of your queries must pay attention to it. You just delay your problem.
So either clean it up (by deleting or merging) or move the old data in a separate table.
You can use SQL select ... group by to find your duplicates, so you can delete/move them in one turn.

(Oracle) How to extract a specialized UDO from a object table based on a generalized UDO?

I apologize in advance for not knowing the terminology, but here it goes:
How do I extract a specialized UDO (UDO= user defined object & specialized= that 'extends' or is 'under' another UDO) that I inserted in a object table defined for a generalized UDO (generalized - higher in the same UDO hierarchy).
Details:
CREATE OR REPLACE TYPE GENERIC_UDO AS OBJECT (
atribute_1 TYPE,
...
atribute_n TYPE2, //TYPE2 is a nested table
) NOT FINAL NOT INSTANTIABLE;
CREATE OR REPLACE TYPE SPECIALIZED_UDO UNDER GENERIC_UDO (
atribute_1 TYPE,
...
atribute_q TYPE3, //TYPE3 is also a nested table
) FINAL INSTANTIABLE;
CREATE TABLE TBL_GENERIC_UDO OF GENERIC_UDO
( CONSTRAINT PK...)
... //other nested tables
NESTED TABLE atribute_n STORE AS atribute_n_nst;
So this works:
INSERT INTO TBL_GENERIC_UDO values (new TBL_GENERIC_UDO(...)); because TBL_GENERIC_UDO is 'under' GENERIC_UDO.
A 'select * from TBL_GENERIC_UDO' shows me as columns just the GENERIC_UDO atributes (as expected!)
So how do I extract or extract and convert, because I need to read back the inserted SPECIALIZED_UDO.
I payed a bit with:
VALUE(x): SELECT VALUE(tgo) FROM TBL_GENERIC_UDO tgo;
TREAT(x as Y): SELECT TREAT(VALUE(tgo) as SPECIALIZED_UDO) FROM TBL_GENERIC_UDO tgo;
and no good results. Actually in booth cases I get
SCHEMANAME.SPECIALIZED_UDO(values, values,..., null)
oracle.sql.STRUCT#54faa2
In the first case, when I did the insert, the corespondent value for atribute_q was set to null, in the second one it was a proper value, ex: SPECIALIZED_UDO('value1','value1',...)
I am asumming thats because TBL_GENERIC_UDO doesnt have a store clause for atribute_q from SPECIALIZED_UDO.
So anyone thoughts, explanations would be appreciated.
Solved it myself! Wasn't that hardcore as I expected. First I thought that Oracle will 'truncate' when inserting a specialized UDO into the table defined for a generic UDO, and in my case the specialized UDO had additionally another nested table as a atribute, but it doesn't.
Check code example, last line is the most important one:
CREATE OR REPLACE TYPE OBJ_TBL_TEXT IS TABLE OF VARCHAR2(100);
CREATE OR REPLACE TYPE OBJ_CONTAINER1 AS OBJECT (
ATRIBUTE NUMBER(6),
TBL_TEXT OBJ_TBL_TEXT
) NOT FINAL NOT INSTANTIABLE;
CREATE OR REPLACE TYPE OBJ_CONTAINER2 UNDER OBJ_CONTAINER1(
SECOND_TBL_TEXT OBJ_TBL_TEXT
) FINAL INSTANTIABLE;
CREATE TABLE TBL_CONTAINER1 OF OBJ_CONTAINER1
( CONSTRAINT PK_TBL_CONTAINER1 PRIMARY KEY(ATRIBUTE))
NESTED TABLE TBL_TEXT STORE AS TBL_TEXT_NST;
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(1,null,null));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(2,null, OBJ_TBL_TEXT('VAL1','VAL2')));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(3,OBJ_TBL_TEXT('VAL3','VAL4'), null));
INSERT INTO TBL_CONTAINER1 VALUES (NEW OBJ_CONTAINER2(4,OBJ_TBL_TEXT('VAL5','VAL6'), OBJ_TBL_TEXT('VAL7','VAL8')));
--------------------------------------------------------------------------------
SELECT * FROM TBL_CONTAINER1;
SELECT VALUE(tc) FROM TBL_CONTAINER1 tc;
SELECT TREAT(VALUE(tc) as OBJ_CONTAINER2).SECOND_TBL_TEXT FROM TBL_CONTAINER1 tc;
First select will display as columns just the atributes from the OBJ_CONTAINER1, like:
ATTRIBUTE| TBL_TEXT
--===============--
- 1 (null)
- 2 (null)
- 3 VARCHAR(VAL3,VAL4)
- 4 VARCHAR(VAL5,VAL6)
Second one looks weirder
VALUE(TC)
--=====--
DATAMODELER.OBJ_CONTAINER2(1,null,null)
oracle.sql.STRUCT#57ac8e
oracle.sql.STRUCT#1df6f
oracle.sql.STRUCT#f40801
VALUE - returns the object instance for a row of an object table as associated with the correlation_variable (table alias)
Third one lets me have access to nested attributes not mention in the nested clause in the creation of TBL_CONTAINER1.
TREAT(VALUE(tc) as OBJ_CONTAINER2).SECOND_TBL_TEXT
--==============================================--
(null)
VARCHAR(VAL1,VAL2)
(null)
VARCHAR(VAL7,VAL8)
It is exactly what I needed. Dont know yet how Oracle stores the additional attributes, and don't care. Though to share the solution...

How to set default value for column of new created table from select statement in 11g

I create a table in Oracle 11g with the default value for one of the columns. Syntax is:
create table xyz(emp number,ename varchar2(100),salary number default 0);
This created successfully. For some reasons I need to create another table with same old table structure and data. So I created a new table with name abc as
create table abc as select * from xyz.
Here "abc" created successfully with same structure and data as old table xyz. But for the column "salary" in old table "xyz" default value was set to "0". But in the newly created table "abc" the default value is not set.
This is all in Oracle 11g. Please tell me the reason why the default value was not set and how we can set this using select statement.
You can specify the constraints and defaults in a CREATE TABLE AS SELECT, but the syntax is as follows
create table t1 (id number default 1 not null);
insert into t1 (id) values (2);
create table t2 (id default 1 not null)
as select * from t1;
That is, it won't inherit the constraints from the source table/select. Only the data type (length/precision/scale) is determined by the select.
The reason is that CTAS (Create table as select) does not copy any metadata from the source to the target table, namely
no primary key
no foreign keys
no grants
no indexes
...
To achieve what you want, I'd either
use dbms_metadata.get_ddl to get the complete table structure, replace the table name with the new name, execute this statement, and do an INSERT afterward to copy the data
or keep using CTAS, extract the not null constraints for the source table from user_constraints and add them to the target table afterwards
You will need to alter table abc modify (salary default 0);
new table inherits only "not null" constraint and no other constraint.
Thus you can alter the table after creating it with "create table as" command
or you can define all constraint that you need by following the
create table t1 (id number default 1 not null);
insert into t1 (id) values (2);
create table t2 as select * from t1;
This will create table t2 with not null constraint.
But for some other constraint except "not null" you should use the following syntax
create table t1 (id number default 1 unique);
insert into t1 (id) values (2);
create table t2 (id default 1 unique)
as select * from t1;

Oracle database table insertion

I have two tables:
create table Number( num number(5));
create table Entry(id number(3), name varchar(50));
How can I increment the num field of Number table in Oracle whenever I insert something in the Entry table?
You should use a SEQUENCE instead. The "Number" table is an inherently bad idea, because when two sessions are inserting rows concurrently, each session only sees the uncommited value in the Number table.
This is what you should do instead:
create sequence entrySeq;
create table Entry(id number(3), name varchar(50));
create trigger tr_entry before insert on Entry for each row
begin
select entrySeq.nextval into :new.number from dual;
end;
/
Do you want number.num to continually represent the number of rows iin the Entry table? If so you could just define it as a view:
create view number_view
as
select count(*) from Entry
create sequence entrySeq;
create table Entry(id number(3), name varchar(50));
insert into Entry value (entrySeq.nextval, 'MyName');
(You don't need a trigger).
A sequence returns a unique and increasing number value but Oracle doesn't guarantuee that it is gapless. When sometimes transactions are rollbacked the values of column id will contain gaps.

Resources