I have a couple of tables with primary-foreign key relations but those constraints do not actually exist. Now I want to add them with an alter table statement.
Will those commands cause any object that depends on the tables to become invalid?
Thanks.
This is a good question. Let's poke the database and see. Here's the set up:
SQL> create table p23 (id number not null, col1 varchar2(10));
Table created.
SQL> create table c23 (id number not null, p_id number not null, col1 varchar2(10));
Table created.
SQL> create or replace procedure tst23
2 is
3 begin
4 insert into p23 values (1, 'ABC');
5 insert into c23 values (11, 1, 'DEF');
6 end;
7 /
Procedure created.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL>
Everything is copacetic. Now we'll add some constraints.
SQL> alter table p23 add constraint p23_pk primary key (id);
Table altered.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL> alter table c23 add constraint c23_p23_fk
2 foreign key (p_id) references p23;
Table altered.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL>
All is still cool. But when we change a table's structure this happens...
SQL> alter table p23 add col2 date;
Table altered.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
INVALID
SQL>
... which is exactly what we would want.
Note that I ran these tests on 11gR2. Oracle introduced fine-grained dependency tracking in 11g, which made programmatic objects more robust when we run DDL on their dependencies. Find out more. So the outcome might be different in earlier versions. It pays to test.
" what is the reason for the procedure to become invalid?"
Structural changes such as adding, modifying or dropping a column potentially have an impact on referencing objects. The procedure had insert statements which didn't specify target columns. So adding a column introduced an ORA-00947: not enough values bug.
But suppose we employed good practice and specified columns?
SQL> create or replace procedure tst23
2 is
3 begin
4 insert into p23 (id, col1) values (1, 'ABC');
5 end;
6 /
Procedure created.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL> alter table p23 add col4 number;
Table altered.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL>
Now we're protected by fine-grained dependency tracking. Well, sort of. We shouldn't substitute dependency tracking for impact analysis:
SQL> alter table p23 add col5 date not null;
Table altered.
SQL> select status from user_objects where object_name = 'TST23';
STATUS
-------
VALID
SQL>
The procedure has a VALID state but it will still fail when we run it, because it doesn't populate the new mandatory column.
Related
I have this SQL Query and I am trying to modify my column so it has an auto increment Property, But when I execute the query I either have a problem code ORA-00933 SQL command not properly ended
This is what I have tried
ALTER TABLE BF_USER DROP COLUMN USER_ID
ALTER TABLE BF_USER ADD USER_ID INT IDENTITY(1,1);
SQL*Plus is Oracle's command-line tool. In that case:
SQL> create table bf_user (user_id number, name varchar2(10));
Table created.
SQL> alter table bf_user drop column user_id;
Table altered.
SQL> alter table bf_user add user_id number generated always as identity;
Table altered.
Testing:
SQL> insert into bf_user(name) values ('Littlefoot');
1 row created.
SQL> select * from bf_user;
NAME USER_ID
---------- ----------
Littlefoot 1
SQL>
As you use Oracle 10g (which doesn't support identity columns), use combination of a sequence and a database trigger:
SQL> create table bf_user (user_id number, name varchar2(10));
Table created.
SQL> create sequence bf_seq;
Sequence created.
SQL> create or replace trigger trg_bi_bfu
2 before insert on bf_user
3 for each row
4 begin
5 select bf_seq.nextval into :new.user_id from dual;
6 end;
7 /
Trigger created.
SQL> insert into bf_user (name) values ('Nayeon');
1 row created.
SQL> select * from bf_user;
USER_ID NAME
---------- ----------
1 Nayeon
SQL>
I have a table that has a unique increment column created using command "GENERATED AS IDENTITY" and the data type is NUMBER(20,0).
Now, the dev want to change the data type to NUMBER(19,0). Since there are hundreds of tables and millions of data, creating a new set of tables just for one column change is not ideal.
So, I manage to create a workflow as follows:
ALTER TABLE my_schema.my_table
ADD REC_ID_TEMP NUMBER(19,0);
UPDATE my_schema.my_table
SET REC_ID_TEMP = REC_ID;
ALTER TABLE my_schema.my_table
DROP COLUMN REC_ID;
ALTER TABLE my_schema.my_table
ADD REC_ID NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY;
UPDATE my_schema.my_table
SET REC_ID = REC_ID_TEMP;
ALTER TABLE my_schema.my_table
MODIFY REC_ID NUMBER(19,0) GENERATED AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1515 //Must start with the last REC_ID value +1// CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE;
This is the best way possible that I can think of with this peanut size brain of mine.
This way, I can make sure that the REC_ID is the exact same as before I change the data type.
But, the problem is, if you read my the last line of my script, I have to check the last REC_ID for the hundreds of table before I can run the last script. I need help to figure out a way that I can retain the original state.
You can use the START WITH LIMIT VALUE clause instead of specifying a number. From the documentation:
START WITH LIMIT VALUE, which is specific to identity_options, can only be used with ALTER TABLE MODIFY. If you specify START WITH LIMIT VALUE, then Oracle Database locks the table and finds the maximum identity column value in the table (for increasing sequences) or the minimum identity column value (for decreasing sequences) and assigns the value as the sequence generator's high water mark. The next value returned by the sequence generator will be the high water mark + INCREMENT BY integer for increasing sequences, or the high water mark - INCREMENT BY integer for decreasing sequences.
A little automation can make this as easily as calling a procedure per table, eg
SQL>
SQL> create table t1 as select * from scott.emp;
Table created.
SQL> create table t2 as select * from dba_objects;
Table created.
SQL> create table t3 as select * from dba_procedures;
Table created.
SQL>
SQL> alter table t1 add rec_id number(20) generated by default as identity start with 100000;
Table altered.
SQL> alter table t2 add rec_id number(20) generated by default as identity start with 100000;
Table altered.
SQL> alter table t3 add rec_id number(20) generated by default as identity start with 100000;
Table altered.
SQL>
SQL> update t1 set rec_id = rownum;
14 rows updated.
SQL> update t2 set rec_id = rownum;
81264 rows updated.
SQL> update t3 set rec_id = rownum;
37644 rows updated.
SQL>
SQL> alter table t1 add primary key ( rec_id);
Table altered.
SQL> alter table t2 add primary key ( rec_id);
Table altered.
SQL> alter table t3 add primary key ( rec_id);
Table altered.
SQL>
SQL> alter table t1 modify rec_id number(20) generated always as identity start with 100000;
Table altered.
SQL> alter table t2 modify rec_id number(20) generated always as identity start with 100000;
Table altered.
SQL> alter table t3 modify rec_id number(20) generated always as identity start with 100000;
Table altered.
SQL>
SQL> create or replace
2 procedure fix_up_my_recid(p_table varchar2) is
3 begin
4 execute immediate 'alter table '||p_table||' add tmp$rec_id number(19,0)';
5
6 execute immediate 'update '||p_table||' set tmp$rec_id = rec_id';
7
8 execute immediate 'alter table '||p_table||' set unused column rec_id';
9
10 execute immediate 'alter table '||p_table||' add rec_id number(19,0) generated by default as identity';
11
12 execute immediate 'update '||p_table||' set rec_id = tmp$rec_id';
13
14 execute immediate 'alter table '||p_table||' set unused column tmp$rec_id';
15
16 execute immediate 'alter table '||p_table||' move online';
17
18 execute immediate 'alter table '||p_table||' modify rec_id number(19,0) generated always as identity start with limit value';
19
20 end;
21 /
Procedure created.
SQL> sho err
No errors.
SQL>
SQL>
SQL>
SQL> desc t1
Name Null? Type
----------------------------------------------------------------------- -------- ------------------------------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
REC_ID NOT NULL NUMBER(20)
SQL> exec fix_up_my_recid('T1')
PL/SQL procedure successfully completed.
SQL> exec fix_up_my_recid('T2')
PL/SQL procedure successfully completed.
SQL> exec fix_up_my_recid('T3')
PL/SQL procedure successfully completed.
SQL> desc t1
Name Null? Type
----------------------------------------------------------------------- -------- ------------------------------------------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
REC_ID NOT NULL NUMBER(19)
SQL>
SQL>
SQL>
SQL>
SQL> desc t2
Name Null? Type
----------------------------------------------------------------------- -------- ------------------------------------------------
OWNER VARCHAR2(128)
OBJECT_NAME VARCHAR2(128)
SUBOBJECT_NAME VARCHAR2(128)
OBJECT_ID NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(23)
CREATED DATE
LAST_DDL_TIME DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)
NAMESPACE NUMBER
EDITION_NAME VARCHAR2(128)
SHARING VARCHAR2(18)
EDITIONABLE VARCHAR2(1)
ORACLE_MAINTAINED VARCHAR2(1)
APPLICATION VARCHAR2(1)
DEFAULT_COLLATION VARCHAR2(100)
DUPLICATED VARCHAR2(1)
SHARDED VARCHAR2(1)
CREATED_APPID NUMBER
CREATED_VSNID NUMBER
MODIFIED_APPID NUMBER
MODIFIED_VSNID NUMBER
REC_ID NOT NULL NUMBER(19)
SQL> desc t3
Name Null? Type
----------------------------------------------------------------------- -------- ------------------------------------------------
OWNER VARCHAR2(128)
OBJECT_NAME VARCHAR2(128)
PROCEDURE_NAME VARCHAR2(128)
OBJECT_ID NUMBER
SUBPROGRAM_ID NUMBER
OVERLOAD VARCHAR2(40)
OBJECT_TYPE VARCHAR2(13)
AGGREGATE VARCHAR2(3)
PIPELINED VARCHAR2(3)
IMPLTYPEOWNER VARCHAR2(128)
IMPLTYPENAME VARCHAR2(128)
PARALLEL VARCHAR2(3)
INTERFACE VARCHAR2(3)
DETERMINISTIC VARCHAR2(3)
AUTHID VARCHAR2(12)
RESULT_CACHE VARCHAR2(3)
ORIGIN_CON_ID NUMBER
POLYMORPHIC VARCHAR2(5)
REC_ID NOT NULL NUMBER(19)
SQL>
Note that I've not used DROP COLUMN because that's incredibly expensive. Generally better to just go with SET UNUSED, and I've thrown in an MOVE ONLINE at the end because all those updates could make a mess of your table row structure.
One question about ORACLE stored procedures and functions:
I added the new column to the table. This table involve in many stored procedures and functions without using a new column. Do I have to recompile function and procedures, which are using the table, even a new column is not involve in the query of those SP and function? Also do I have to update statistics and so on?
I understand, theoretical question, but anyway.
Please advise.
Thanks
1- if source objects are changed, stored procedures and functions which depend of them must be compile again.
2- If you want to learn which sp or function depends your table, you can try something like;
select * from USER_SOURCE where text like '%your_table_name%';
select * from DBA_SOURCE where text like '%your_table_name%';
select * from ALL_SOURCE where text like '%your_table_name%';
3- You must not collect statics but it will be useful aggregate and update histograms for performance.
It depends on what you change. You can look at the all_dependencies view (or the user_ or dba_ equivalents) to see what may be affected, and the user_objects view to see if something has been made invalid for any reason:
create table t42 (col1 varchar2(10));
insert into t42 (col1) values ('val1');
create procedure p42 as
l_col1 t42.col1%type;
begin
select col1 into l_col1 from t42;
dbms_output.put_line(l_col1);
end p42;
/
column object_name format a12
select object_type, object_name, status
from user_objects
where object_name in ('T42', 'P42');
OBJECT_TYPE OBJECT_NAME STATUS
------------------- ------------ -------
PROCEDURE P42 VALID
TABLE T42 VALID
select type, name, dependency_type
from user_dependencies
where referenced_type = 'TABLE' and referenced_name = 'T42';
TYPE NAME DEPE
------------------ ------------------------------ ----
PROCEDURE P42 HARD
Adding a column doesn't invalidate the procedure in this case:
alter table t42 add (col2 varchar2(10));
select object_type, object_name, status
from user_objects
where object_name in ('T42', 'P42');
OBJECT_TYPE OBJECT_NAME STATUS
------------------- ------------ -------
PROCEDURE P42 VALID
TABLE T42 VALID
Modifying the column used by the procedure does though:
alter table t42 modify (col1 varchar2(20));
select object_type, object_name, status
from user_objects
where object_name in ('T42', 'P42');
OBJECT_TYPE OBJECT_NAME STATUS
------------------- ------------ -------
PROCEDURE P42 INVALID
TABLE T42 VALID
But you don't have to explicitly recompile the procedure; you can if you want to, but Oracle will automatically try to recompile an invalid object when it is referenced:
exec p42;
PL/SQL procedure successfully completed.
select object_type, object_name, status
from user_objects
where object_name in ('T42', 'P42');
OBJECT_TYPE OBJECT_NAME STATUS
------------------- ------------ -------
PROCEDURE P42 VALID
TABLE T42 VALID
You can also use PL/Scope to get more information and details about dependencies too.
I need to create a trigger that writes changes in a shadow table. I know how to create the trigger but my challenge is that I need the records in the new table to exist even after a rollback.
This is an example of how the output will look like
INSERT INTO department VALUES (95, 'PURCHASING', 'CHICAGO');<br>
ROLLBACK;
1 rows inserted.
rollback complete.
SELECT * FROM department_log;
DEPARTMENT_ID DEPARTMENT_NAME ADDRESS OPERATION_TIME
---------------------- -------------------- -------------------- ------------------
90 HR CHICAGO 03-NOV-11
95 PURCHASING CHICAGO 03-NOV-11
SELECT * from department WHERE department_id >= 90;
DEPARTMENT_ID DEPARTMENT_NAME ADDRESS
---------------------- -------------------- --------------------
90 HR CHICAGO
You'll need to use autonomous transactions.
SQL> create table t (col1 number);
Table created.
SQL> create table t_shadow( col1 number, dt date );
Table created.
SQL> create trigger trg_t
2 before insert on t
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into t_shadow( col1, dt )
8 values( :new.col1, sysdate );
9 commit;
10 end;
11 /
Trigger created.
SQL> insert into t values( 1 );
1 row created.
SQL> rollback;
Rollback complete.
SQL> select * from t;
no rows selected
SQL> select * from t_shadow;
COL1 DT
---------- ---------
1 09-NOV-11
Note that if you find yourself using autonomous transactions for anything other than persistent logging, you are almost certainly doing something wrong. Autonomous transactions are a very dangerous and very frequently misused feature.
You would need to declare the trigger as an Autonomous Transaction
PRAGMA AUTONOMOUS_TRANSACTION;
This decouples the trigger code from the main transaction, so even if the main insertion into the table (which fired the trigger) rollsback, the trigger is executed in a different transactional context and can commit / rollback independently.
In Postgresql, if I do ALTER TABLE mytable DISBLE TRIGGERS ALL, all triggers and constraints regarding this table are suspended.
Especially, foreign keys from other tables to mytable are suspended, and I can delete from mytable without problem. I have the risk to break database consistency, but I know what I'm doing, and I must have superuser privileges.
How do I do the same in Oracle ? I am under the impression that ALTER TABLE mytable DISBLE ALL TRIGGERS in Oracle will suspend all trigger and constraints belonging to mytable, but not those that concerns mytable but belong to other tables (especially foreign keys).
Am I right and what would be the way to achieve the same result as in Postgresql in Oracle ?
The syntax does disable triggers in Oracle:
SQL> select trigger_name, status from user_triggers
2 where table_name='TEST'
3 /
TRIGGER_NAME STATUS
------------------------------ --------
TEST_TRIGGER ENABLED
SQL> ALTER TABLE test DISABLE ALL TRIGGERS
2 /
Table altered.
SQL> select trigger_name, status from user_triggers
2 where table_name='TEST'
3 /
TRIGGER_NAME STATUS
------------------------------ --------
TEST_TRIGGER DISABLED
SQL>
However it won't do anything for foreign keys, or indeed any other constraint. That's because Oracle doesn't use triggers to enforce such things. Okay, under the covers constraints and user-defined triggers may share certain low-level kernel code. But at the level we're talking they are two different things.
If you want to disable all foreign keys on a table I'm afraid you'll need to use something like this:
SQL> select constraint_name, status from user_constraints
2 where table_name = 'EMP'
3 and constraint_type = 'R'
4 /
CONSTRAINT_NAME STATUS
------------------------------ --------
FK_DEPTNO ENABLED
SQL> begin
2 for r in ( select constraint_name, status from user_constraints
3 where table_name = 'EMP'
4 and constraint_type = 'R' )
5 loop
6 execute immediate 'alter table emp disable constraint '||r.constraint_name;
7 end loop;
8* end;
9 /
PL/SQL procedure successfully completed.
SQL> select constraint_name, status from user_constraints
2 where table_name = 'EMP'
3 and constraint_type = 'R'
4 /
CONSTRAINT_NAME STATUS
------------------------------ --------
FK_DEPTNO DISABLED
SQL>
This is the sort of thing you'll probably want to wrap in a user-defined function, which takes TABLE_NAME as a parameter. Also you'll need a similar function to re-enable the constraints.