Syntax for creating foreign key index inline in create table statement - oracle

I want to create a table bar with a named foreign key constraint backed by a named index. I would like to do that with an inline definition in the create table DDL statement. When looking at the Oracle 19 SQL Language Reference it looks like Oracle should support doing this inline.
When executing the following statements...
create table foo (
id number not null primary key
);
create table bar (
id number not null primary key,
nick varchar2(16) not null constraint foo_nick_ck unique using index,
foo_id number not null constraint foo_fk references foo using index
);
Oracle will respond with [42000][907] ORA-00907: missing right parenthesis and point at the position just before the using index on the last line. If I remove using index it works (but without index being created of course). I've kept the column nick as an example of where it works to create the backing index inline, but for a unique constraint instead of a foreign key constraint.
I am aware that a workaround is to create the backing index in a separate DDL statement, but I would very much like to have it neat and tidy inline.

I am aware that a workaround is to create the backing index in a separate DDL statement, but I would very much like to have it neat and tidy inline.
Unfortunately the syntax does not exist to create indexes inline.
The USING INDEX clause is syntactic sugar: Oracle creates indexes to enforce Primary Key and Unique constraints regardless of whether we include that clause (*). This is because an index is necessary to Oracle's implementation of unique constraints. But there is no such need for an index to enforce a foreign key, it's just desirable.
(*) Unless there's an existing index on the constrained column(s) which Oracle can use.

As per Oracle documentation:
You can specify the using_index_clause only when enabling unique or
primary key constraints
You cannot specify this clause for a NOT NULL, foreign key, or
check constraint.

Related

Make an Oracle foreign key constraint referencing USER_SEQUENCES(SEQUENCE_NAME)?

I want to create a table with a column that references the name of a sequence I've also created. Ideally, I'd like to have a foreign key constraint that enforces this. I've tried
create table testtable (
sequence_name varchar2(128),
constraint testtableconstr
foreign key (sequence_name)
references user_sequences (sequence_name)
on delete set null
);
but I'm getting a SQL Error: ORA-01031: insufficient privileges. I suspect either this just isn't possible, or I need to add something like on update cascade. What, if anything, can I do to enforce this constraint when I insert rows into this table?
I assume you're trying to build some sort of deployment management system to keep track of your schema objects including sequences.
To do what you ask, you might explore one of the following options:
Run a report after each deployment that compares the values in your table vs. the data dictionary view, and lists any discrepancies.
Create a DDL trigger which does the insert automatically whenever a sequence is created.
Add a trigger to the table which does a query on the sequences view and raises an exception if not found.
I'm somewhat confused at what you are trying to achieve here - a sequence (effectively) only has a single value, the next number to be allocated, not all the values that have been previously allocated.
If you simply want to ensure that an attribute in the relation is populated from the sequence, then a trigger would be the right approach.

Why dropping a primary key is not dropping its unique index?

I have a table with Col1 and Col2 as a composite primary key pk_composit_key and a unique index that was automatically created for the constraint.
I then altered the table to add new column Col3.
I dropped the pk_composit_key constraint:
ALTER TABLE table_name DROP CONSTRAINT pk_composit_key;
Now, When I tried to insert records I got ORA-00001: unique constraint pk_composit_key violated.
Why am I getting that error?
When the key was dropped why wasn't the unique index dropped automatically?
You mentioned exporting and importing the schema, and if that happened in the environment that showed this behaviour it would explain what you're seeing; at least if you used legacy imp rather than the data pump impdp.
The original import documentation states the order objects are imported:
Table objects are imported as they are read from the export dump file. The dump file contains objects in the following order:
Type definitions
Table definitions
Table data
Table indexes
Integrity constraints, views, procedures, and triggers
Bitmap, function-based, and domain indexes
So the unique index would have been imported, then the constraint would have been created.
When you drop a primary key constraint:
If the primary key was created using an existing index, then the index is not dropped.
If the primary key was created using a system-generated index, then the index is dropped.
Because of the import order, the constraint is using an existing index,so the first bullet applies; and the index is retained when the constraint is dropped.
You can use the drop index clause to drop the index even if it wasn't created automatically:
ALTER TABLE table_name DROP CONSTRAINT pk_composit_key DROP INDEX;
See also My Oracle Support note 370633.1; and 1455492.1 suggests similar behaviour will occur with data pump import as well. I'm not aware of any way to check if an index is associated with a constraint at this level; there is no difference in the dba_constraints or dba_indexes views when you create the index manually or automatically. Including drop index will make it consistent though.
It depends on how unique index was created...below are the various ways and behaviour
1) first create unique index (on the column for which primary key to be defined) and then add the primary key constraint. In this situation your DDL to add the primary key will utilize the existing unique index. So when you drop the primary key it will not drop the index but only primary key. ==> this is your situation I guess...
2) While creating the table you define the primary key OR when you add the primary key when there was no existing unique index for the column(s) on which primary key to be defined, so system will create a unique index and use it for primary key. So in this case when you drop the primary key the unique index will also get dropped.

ORA-00955 "name is already used by an existing object"

I need to modify an existing PK. Therefore I drop an recreate it.
ALTER TABLE B DROP CONSTRAINT PK_B;
ALTER TABLE B ADD CONSTRAINT PK_B PRIMARY KEY ("TYP", "NR", "HH", "QUART");
Unfortunately the last Statement will give me an error ORA-00955
If I create the PK constraint like it was defined originally with:
ALTER TABLE B ADD CONSTRAINT PK_B PRIMARY KEY ("TYP", "NR", "HH");
everything works fine.
Perhaps there is an INDEX associated with the PRIMARY KEY CONSTRAINT, and it is also named as PK_B.
You can check it as :
SELECT * FROM USER_INDEXES WHERE TABLE_NAME='<table_name>';
If that's true, then do :
ALTER INDEX "PK_B" RENAME TO "PK_XYZ";
Update : Regarding ALTER INDEX statement, few important points as mentioned by Justin in the comments
Oracle implicitly creates an UNIQUE index to support the PRIMARY KEY CONSTRAINT. Since, the index is of the same name that of the primary key, and now that the primary key is being modified, it is better to drop and re-create the index again as per the definition of the old primary key.
My conclusion :
The primary key constraint is enforced through a unique index.
If Oracle already finds an index – unique or non-unique – it uses it
for the primary key.
If the index was initially created as non-unique, it will continue to
show as non-unique, however it will actually be a unique index.
A good demonstration and quite detailed on other aspects too, by Arup : Primary Keys Guarantee Uniqueness? Think Again.
I had the same issue where I had to do the following to delete reference to a table from the view whilst recreating the database from the scratch. I was searching for the same in tables and indexes first.
connect sys/oracle as sysdba;
select * from all_tables
select * from all_indexes
(finally located the reference in the views)
select * from all_views where view_name like '%WKSTSTATE%';
drop view RUEGEN.WKSTSTATE;

Case-insensitive primary key in Oracle

The semantic of our data is case insensitive, so we configure the oracle sessions to be case insensitive:
alter session set NLS_COMP=LINGUISTIC;
alter session set NLS_SORT=BINARY_AI;
Then, to take advantage of indexes we would also want the primary key to be case insensitive as well:
create table SCHEMA_PROPERTY (
NAME nvarchar2(64) not null,
VALUE nvarchar2(1024),
constraint SP_PK primary key (nlssort(NAME))
)
However, this runs into "ORA-00904: : invalid identifier", so I assume it is not possible to use the nlssort() function in the PK definition.
Next attempt was to associate a case-insensitive unique index to the primary key:
create table SCHEMA_PROPERTY (
NAME nvarchar2(64) primary key using index (
create unique index SP_UQ on SCHEMA_PROPERTY(nlssort(NAME))),
VALUE nvarchar2(1024)
);
but this failed too:
Error: ORA-14196: Specified index cannot be used to enforce the constraint.
14196. 00000 - "Specified index cannot be used to enforce the constraint."
*Cause: The index specified to enforce the constraint is unsuitable
for the purpose.
*Action: Specify a suitable index or allow one to be built automatically.
Should I just conclude that Oracle does not support case-insensitive semantics for a PK constraint? This works fine in MSSQL which has a simpler approach in dealing with collations.
We could, of course, create a unique index instead of the primary key, but I wanted to make sure first that the normal way to do this is not supported.
Our oracle version is 11.2.0.1.
As you are on 11.2 you can use a virtual column to achieve this:
CREATE TABLE SCHEMA_PROPERTY (
REAL_NAME nvarchar2(64) not null,
NAME generated always as (lower(real_name)) primary key,
VALUE nvarchar2(1024)
);
Create a unique index to enforce case-insensitive PK:
create table SCHEMA_PROPERTY (
NAME nvarchar2(64),
VALUE nvarchar2(1024),
constraint SP_PK primary key (NAME)
);
create unique index SP_UN on SCHEMA_PROPERTY(lower(NAME));

Create constraint in alter table without checking existing data

I'm trying to create a constraint on the OE.PRODUCT_INFORMATION table which is delivered with Oracle 11g R2.
The constraint should make the PRODUCT_NAME unique.
I've tried it with the following statement:
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME);
The problem is, that in the OE.PRODUCT_INFORMATION there are already product names which currently exist more than twice.
Executing the code above throws the following error:
an alter table validating constraint failed because the table has
duplicate key values.
Is there a possibility that a new created constraint won't be used on existing table data?
I've already tried the DISABLED keyword. But when I enable the constraint then I receive the same error message.
You can certainly create a constraint which will validate any newly inserted or updated records, but which will not be validated against old existing data, using the NOVALIDATE keyword, e.g.:
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME)
NOVALIDATE;
If there is no index on the column, this command will create a non-unique index on the column.
If you are looking to enforce some sort of uniqueness for all future entries whilst keeping your current duplicates you cannot use a UNIQUE constraint.
You could use a trigger on the table to check the value to be inserted against the current table values and if it already exists, prevent the insert.
http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_triggers.htm
or you could just remove the duplicate values and then enfoce your UNIQUE constraint.
EDIT: After Jonearles and Jeffrey Kemp's comments, I'll add that you can actually enable a unique constraint on a table with duplicate values present using the NOVALIDATE clause but you'd not be able to have a unique index on that constrained column.
See Tom Kyte's explanation here.
However, I would still worry about how obvious the intent was to future people who have to support the database. From a support perspective, it'd be more obvious to either remove the duplicates or use the trigger to make your intent clear.
YMMV
You can use deferrable .
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME)
deferrable initially deferred NOVALIDATE;

Resources