Alter table enable novalidate constraint - oracle

I am trying to add UNIQUE KEY on a previously existing table with duplicate records by setting ENABLE NOVALIDATE.
But I am getting ORA-02299: cannot validate (my_owner.my_key_UK ) - duplicate keys found
ALTER TABLE my_owner.my_table
ADD CONSTRAINT my_key_UK UNIQUE (ID1,ID2)
ENABLE NOVALIDATE;

A unique constraint uses an index to enforce the noduplicates rule. By default it will create a unique index (makes sense right?). It is this index creation which is hurling ORA-02299.
However, if this is an existing index on the constrained columns the constraint will use that. The good news is, the index doesn't need to be unique for the constraint to use it.
So what you need to do is build a non-unique index first:
create index whatever_idx on my_table (ID1,ID2);
Then you will be able to create your constraint:
ALTER TABLE my_owner.my_table
ADD CONSTRAINT my_key_UK UNIQUE (ID1,ID2)
ENABLE NOVALIDATE;
You can check this by querying the data dictionary:
select uc.constraint_name
, uc.constraint_type
, uc.index_name
, ui.uniqueness as idx_uniqueness
from user_constraints uc
join user_indexes ui
on ui.index_name = uc.index_name
where uc.table_name = 'MY_TABLE'

Oracle ensure unique values using indexes. And if you create unique constrains db automatic creates unique index. Workaround is add DEFERRABLE options. In this case oracle creates normal index. Check example.
create table table_abc (a number,b number);
insert into table_abc values(1,1);
insert into table_abc values(1,2);
ALTER TABLE table_abc ADD CONSTRAINT my_key_a UNIQUE (a) DEFERRABLE enable novalidate; -- no error but in table nonunique values
ALTER TABLE table_abc ADD CONSTRAINT my_key_b UNIQUE (b) ENABLE NOVALIDATE; --no error
select * from user_indexes where table_name ='TABLE_ABC';

Related

Oracle foreign key constraints - check constraint syntax?

I have a child table in oracle that has two foreign key columns, relating to two different parent tables. I want to create a constraint that says the child must have at least one of those parents - e.g.
ALTER TABLE table_name
ADD CONSTRAINT constraint_name
FOREIGN KEY (column1)
REFERENCES parent_table (column1)
OR
FOREIGN KEY (column2)
REFERENCES parent_table_2 (column1)
This won't work with a foreign key constraint because that can only relate to one parent table - is it possible to do this with a check constraint instead?
Foreign key constraints ensure the referential integrity, not mandatory values.
I think you have to have to separate FK contraints and additional check constraint like this:
alter table table_name
add constraint c_check_cols
check(column1 is not null or column2 is not null);
You can do it with constraints:
ALTER TABLE table_name
ADD CONSTRAINT constraint_name_1
FOREIGN KEY (column1)
REFERENCES parent_table (column1);
ALTER TABLE table_name
ADD CONSTRAINT constraint_name_2
FOREIGN KEY (column2)
REFERENCES parent_table_2 (column1);
ALTER TABLE table_name
ADD CONSTRAINT constraint_name_3
check (COALESCE(column1, column2) IS NOT NULL);
Of course for column1 and column2 column you must permit NULL values.

Oracle: ORA-01408: such column list already indexed

I have created a table:
CREATE TABLE SCHEMA_NAME.TABLE_NAME
(
ID VARCHAR2(100) PRIMARY KEY NOT NULL,
A_ID VARCHAR2(100) NOT NULL,
B_ID VARCHAR2(100),
C_ID INTEGER,
CONSTRAINT FK_C_NOTE FOREIGN KEY (C_ID) REFERENCES C(ID),
CONSTRAINT FK_A_NOTE FOREIGN KEY (A_ID) REFERENCES A(ID),
CONSTRAINT FK_B_NOTE FOREIGN KEY (B_ID) REFERENCES B(ID)
);
And I have tried to apply the following indexes:
CREATE INDEX IDX_FK_A_TABLE_NAME on SCHEMA_NAME.TABLE_NAME(A_ID) tablespace TS_SCHEMA_NAME_DATA;
CREATE INDEX IDX_FK_C_TABLE_NAME on SCHEMA_NAME.TABLE_NAME(C_ID) tablespace TS_SCHEMA_NAME_DATA; --Fails
CREATE INDEX IDX_FK_B_TABLE_NAME on SCHEMA_NAME.TABLE_NAME (B_ID) tablespace TS_SCHEMA_NAME_DATA; --Fails
But when I try to create the last two indexes I get:
ORA-01408: such column list already indexed
Why is this? Are these indexes created automatically?
If I try:
SELECT INDEX_NAME FROM ALL_INDEXES WHERE TABLE_NAME = 'NOTE';
I get:
INDEX_NAME
----------
IDX_FK_A_TABLE_NAME
SYS_C0044692561
Double check you indexed columns by querying ALL_IND_COLUMN (or DBA_IND_COLUMNS if you have acess). It will show you what table columns are indexed by which index.
SELECT c.index_owner, c.index_name, c.table_owner, c.table_name, c.column_name, c.column_position
FROM all_ind_columns c
WHERE c.table_owner = 'SCHEMA_NAME'
AND c.table_name = 'NOTE'
ORDER BY c.index_owner, c.index_name, c.column_position;
On a site note, When you are creating your indexes you're not prefixing them with a schema owner (CREATE INDEX SCHEMA.IDX_FK_A_TABLE_NAME ...). That means they're being created in your current schema. On the other hand, it's probably not related to the error you are facing.

"SET FOREIGN_KEY_CHECKS = 0;" Oracle Equivalent

Is there some equivalent to the Mysql specific instruction that disable the check of the foreign keys constraints ?
SET FOREIGN_KEY_CHECKS = 0;
There is no command in Oracle that will disable all constraints at once.
However it seems you want to disable constraints in the context of dropping tables. In that case you can use the CASCADE CONSTRAINTS clause to drop the referencing constraints from other tables along with the table being dropped.
Here's an example:
SQL> CREATE TABLE t1 (ID NUMBER PRIMARY KEY);
Table created
SQL> CREATE TABLE t2 (ID NUMBER REFERENCES t1);
Table created
SQL> INSERT INTO t1 VALUES (1);
1 row inserted
SQL> INSERT INTO t2 VALUES (1);
1 row inserted
SQL> -- this fails because of the foreign key
SQL> DROP TABLE t1;
ORA-02449: unique/primary keys in table referenced by foreign keys
SQL> DROP TABLE t1 CASCADE CONSTRAINTS;
Table dropped
SET FOREIGN_KEY_CHECKS = 0; is session based. In an Oracle context I can only imagine you needing to do this when you have circular references.
You've commented that this is what you want to do:
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE table1;
DROP TABLE table2;
SET FOREIGN_KEY_CHECKS = 1;
I'm assuming that this means that TABLE1 has a foreign key referencing TABLE2 and TABLE2 has a foreign key referencing TABLE1.
If this is the case then Moudiz's answer is correct. You want to disable the foreign keys before dropping the table:
alter table table1 disable constraint <constraint_name>;
alter table table2 disable constraint <constraint_name>;
drop table table1;
drop table table2;
There's no point disabling all foreign keys for the length of the session, you're only interested in two of them, both of which will be dropped with the table.
You don't want to ever disable all foreign keys.
The only other context I can think of this in is if you want to insert something into your circular reference, in which case you would declare the constraint as DEFERRABLE. This means that the constraint check is done at the end of the transaction rather than on DML being performed, see the documentation.
If your references aren't circular then simply drop the tables in the other order.
IF Your asking for a query to disable a foreign key then try this :
ALTER TABLE mytable
disable CONSTRAINT fk_mytable;

shrinking a column in oracle

Lets say i have a table with the following definition
create table dummy (col1 number(9) not null)
All the values in this dummy.col1 are 7 digit long. Now i want to reduce the length of this column from 9 - 7 using alter command. Oracle gives me error that column to be modified must be empty to decrease precision or scale. Makes sense.
I want to ask is there any work around to reduce the column size?
I can't delete the values in the column.
I can't copy values from this column to another since it has trillions of data.
The column size has no relationship to how the data is physically stored (they are variable length)
e.g. '23' in a number(2) will take exactly the same space if stored in a number(38)
It is purely a constraint on the maximum number that can be stored in the column therefore you could just add a constraint on the column:
ALTER TABLE dummy ADD
CONSTRAINT c1
CHECK (col1 < 9999999)
ENABLE
VALIDATE;
if you want it to go a little quicker change VALIDATE to NOVALIDATE obviously this will not check the validity of the existing data.
Kevin's answer is excellent.
The only other way to do it is to
rename the existing column,
create a new column with the old name and the new size,
issue an update statement to populate the new field (which you said you cannot do)
and then drop the renamed column.
Are you sure you cannot find some downtime one weekend to perform this task ?
Solution #1
My solution below keeps the original column order.
I found that to be important, especially if there are canned SQL statements out
there (middle tier, client tier) that point back to your database that do implicit
SELECTs.
i.e.
SELECT *
FROM tableName
WHERE ...;
INSERT INTO copyTableName(column1,column2,column3,...)
SELECT *
FROM tableName
WHERE ...;
Here goes:
Generate the DDLs for
1. The table containing the column you intend to resize
2. All the relationship constraints, indexes, check constraints, triggers that reference that table.
3. All the foreign keys of other tables that reference the primary key of this table.
Make sure each table-referencing-object DDL is stand-alone, separate from the
CREATE TABLE DDL.
You'll have something like
/* 1. The table containing the column you intend to resize */
CREATE TABLE tableName
(
column1 TYPE(size) [DEFAULT value] [NOT] NULL,
column2 TYPE(size) [DEFAULT value] [NOT] NULL,
column3 TYPE(size) [DEFAULT value] [NOT] NULL,
...
)
TABLESPACE tsName
[OPTIONS];
/* 2. All the relationship constraints, indexes, check constraints, triggers that reference that table. */
CREATE INDEX indexName ON tableName
(column1)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
CREATE INDEX compositeIndexName ON tableName
(column1,column2,...)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
CREATE UNIQUE INDEX pkName ON tableName
(column2)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
ALTER TABLE tableName ADD (
CHECK (column4 IS NOT NULL));
ALTER TABLE tableName ADD (
CONSTRAINT pkName
PRIMARY KEY
(column2)
USING INDEX
TABLESPACE INDX);
ALTER TABLE tableName ADD (
CONSTRAINT fkName
FOREIGN KEY (column2)
REFERENCES otherTable (column2));
/* 3. All the foreign keys of other tables that reference the primary key of this table. */
ALTER TABLE otherTableName ADD (
CONSTRAINT otherTableFkName
FOREIGN KEY (otherTableColumn2)
REFERENCES tableName (column1));
Copy out just the CREATE TABLE statement, change the table name and
reduce the size of the column you wish to modify:
CREATE TABLE tableName_YYYYMMDD
(
column1 TYPE(size) [DEFAULT value] [NOT] NULL,
column2 TYPE(reducedSize) [DEFAULT value] [NOT] NULL,
column3 TYPE(size) [DEFAULT value] [NOT] NULL,
...
)
TABLESPACE tsName
[OPTIONS];
Insert the data from tableName into tableName_YYYYMMDD:
INSERT /* APPEND */ INTO tableName_YYYYMMDD(
column1 ,
column2 ,
column3 ,
... )
SELECT
column1 ,
column2 ,
column3 ,
...
FROM tableName;
COMMIT;
Drop all objects referencing the original table.
Also, drop all foreign keys that reference the tableName primary key pkName.
Don't worry, you've saved the DDL so you'll be able to recreate them.
Notice that I drop indexes after copying the data out of tableName.
I do this because perhaps one of the indexes will be used in the
above SELECT so that operation will complete faster.
DROP INDEX indexName ;
DROP INDEX compositeIndexName ;
DROP UNIQUE INDEX pkName ;
ALTER TABLE tableName DROP CONSTRAINT pkName ;
ALTER TABLE tableName DROP CONSTRAINT fkName ;
ALTER TABLE otherTableName DROP CONSTRAINT otherTableFkName ;
Drop the original table.
DROP TABLE tableName;
Rename the new table.
ALTER TABLE tableName_YYYYMMDD RENAME TO tableName;
Recreate all referencing objects from the DDL statements you saved before.
/* 2. All the relationship constraints, indexes, check constraints, triggers that reference that table. */
CREATE INDEX indexName ON tableName
(column1)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
CREATE INDEX compositeIndexName ON tableName
(column1,column2,...)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
CREATE UNIQUE INDEX pkName ON tableName
(column2)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
ALTER TABLE tableName ADD (
CHECK (column4 IS NOT NULL));
ALTER TABLE tableName ADD (
CONSTRAINT pkName
PRIMARY KEY
(column2)
USING INDEX
TABLESPACE INDX);
ALTER TABLE tableName ADD (
CONSTRAINT fkName
FOREIGN KEY (column2)
REFERENCES otherTable (column2));
/* 3. All the foreign keys of other tables that reference the primary key of this table. */
ALTER TABLE otherTableName ADD (
CONSTRAINT otherTableFkName
FOREIGN KEY (otherTableColumn2)
REFERENCES tableName (column1));
Solution #2
Keep the column order but do not rebuild non-unique-used-by-PK indexes that might contain column2.
ALTER TABLE tableName ADD (column2Copy TYPE(reducedSize));
UPDATE tableName SET column2Copy = column2;
ALTER TABLE tableName MODIFY (column2 TYPE(size) NULL);
ALTER TABLE tableName DROP CONSTRAINT pkName;
DROP INDEX pkName;
UPDATE tableName SET column2 = null;
ALTER TABLE tableName MODIFY (column2 TYPE(reducedSize));
UPDATE tableName SET column2 = column2Copy;
ALTER TABLE tableName DROP COLUMN column2Copy;
CREATE UNIQUE INDEX pkName ON tableName
(column2)
NOLOGGING
TABLESPACE INDX
NOPARALLEL;
ALTER TABLE tableName ADD (
CONSTRAINT pkName
PRIMARY KEY
(column2)
USING INDEX
TABLESPACE INDX);
COMMIT;

Oracle Foreign Key Issues with Multi-Table Inserts and Blobs

We have a single table that we want to break up into a tree of tables based upon a particular source column. I wanted to try using a multi-column insert, but it seems that if I insert a blob into a sub table, I wind up with a foreign key constraint violation.
I don't think this violates the rules about multi-table inserts but I could be wrong...
I am hoping that someone could point me to some more in-depth resources around what is actually going on here, so that I can feel confident that whatever solution will work as part of a liquibase changeset on Oracle databases 9i -> 11g.
Hopefully Simplified Scenario
CREATE TABLE source (
pk NUMBER NOT NULL PRIMARY KEY,
type VARCHAR2(20) NOT NULL,
content VARCHAR2(20) NOT NULL
);
INSERT INTO source (pk,type,content) values (1,'two','n/a');
INSERT INTO source (pk,type,content) values (2,'one','Content');
CREATE TABLE dest (
pk NUMBER NOT NULL PRIMARY KEY,
type VARCHAR2(20) NOT NULL
);
CREATE TABLE dest_one (
pkfk NUMBER NOT NULL PRIMARY KEY,
data BLOB NOT NULL,
CONSTRAINT XFK1DEST_ONE FOREIGN KEY (pkfk) REFERENCES dest (pk)
);
CREATE TABLE dest_two (
pkfk NUMBER NOT NULL PRIMARY KEY,
CONSTRAINT XFK1DEST_TWO FOREIGN KEY (pkfk) REFERENCES dest (pk)
);
Source contains our original data. dest will be our parent table, with children dest_one and dest_two (which will contain information on things of type 'one' or 'two' respectively). Things of type one have content, but things of type two do not.
The Failed Attempt
INSERT ALL
WHEN 1=1 THEN INTO dest (pk,type) VALUES (pk,type)
WHEN type='one' THEN INTO dest_one (pkfk,data) VALUES (pk,content)
WHEN type='two' THEN INTO dest_two (pkfk) VALUES (pk)
SELECT pk,type,utl_raw.cast_to_raw(content) as content from source where type in ('one','two');
As previously mentioned, I wound up with a foreign key constraint violation here. To further illustrate that the blob was the issue I tried two seperate similar queries (below) realizing the one without the blob insert worked, but with the blob insert failed.
INSERT ALL
WHEN 1=1 THEN INTO dest (pk,type) VALUES (pk,type)
WHEN type='two' THEN INTO dest_two (pkfk) VALUES (pk)
SELECT pk,type,utl_raw.cast_to_raw(content) as content from source where type = 'two';
/* Successful */
INSERT ALL
WHEN 1=1 THEN INTO dest (pk,type) VALUES (pk,type)
WHEN type='one' THEN INTO dest_one (pkfk,data) VALUES (pk,content)
SELECT pk,type,utl_raw.cast_to_raw(content) as content from source where type = 'one';
/* ORA-02291: integrity constraint violated, no parent key */
Solution 1 - Traditional Inserts
INSERT INTO dest (pk,type) SELECT pk,type from source where type in ('one','two');
INSERT INTO dest_two (pkfk) SELECT pk from source where type = 'two';
INSERT INTO dest_one (pkfk,data) SELECT pk,utl_raw.cast_to_raw(content) from source where type = 'one';
One option I am considering is going back to multiple seperate insert statements, but unlike how I have stated them here, I'm concerned that I'll have to make sure I write my sub-table inserts to only attempt to insert those rows present in parent dest table... I need to do more research on how Liquibase handles multiple sql statements in the same changeset.
Solution 2 - Temporarily disabling foreign key constraints
ALTER TABLE dest_one DISABLE CONSTRAINT XFK1DEST_ONE;
INSERT ALL
WHEN 1=1 THEN INTO dest (pk,type) VALUES (pk,type)
WHEN type='one' THEN INTO dest_one (pkfk,data) VALUES (pk,content)
WHEN type='two' THEN INTO dest_two (pkfk) VALUES (pk)
SELECT pk,type,utl_raw.cast_to_raw(content) as content from source where type in ('one','two');
ALTER TABLE dest_one ENABLE CONSTRAINT XFK1DEST_ONE;
This is the solution I'm leaning toward. While disabling the foreign key on my blob table seems to make it work in my test environment (10g - 10.2.0.1.0), I'm not sure if I should also be disabling the foreign key on the non-blob table as well (due to how 9i, 11g, or other versions of 10g may behave). Any resources here too would be appreciated.
Thanks a bunch!
Another solution would be to defer the constraint evaluation until COMMIT. I suspect (but am not sure) that the multi-table insert is inserting rows in an order other than the one you expect and want. Recreate your constraints as follows:
ALTER TABLE DEST_ONE DROP CONSTRAINT XFK1DEST_ONE;
ALTER TABLE DEST_ONE
ADD CONSTRAINT XFK1DEST_ONE
FOREIGN KEY (pkfk) REFERENCES dest (pk)
INITIALLY DEFERRED DEFERRABLE;
ALTER TABLE DEST_TWO DROP CONSTRAINT XFK1DEST_TWO;
ALTER TABLE DEST_TWO
ADD CONSTRAINT XFK1DEST_TWO
FOREIGN KEY (pkfk) REFERENCES dest (pk)
INITIALLY DEFERRED DEFERRABLE;
This re-creates the constraints so that they can be deferred, and are deferred from the time they're created. Then try your original INSERT again.
Share and enjoy.

Resources