Foreign Keys or Delete Trigger? - oracle

The tables:
SIGN_OBJECT:
ID VARCHAR2(32) PRIMARY KEY,
DESCRIPTION VARCHAR2(100),
X NUMBER(10,3),
Y NUMBER(10,3),
...
GEOMETRYID VARCHAR2(32)
LAMPPOST_OBJECT:
ID VARCHAR2(32) PRIMARY KEY,
DESCRIPTION VARCHAR2(100),
X NUMBER(10,3),
Y NUMBER(10,3),
...
GEOMETRYID VARCHAR2(32)
OBJGEOMETRY:
GEOMETRYID VARCHAR2(32) PRIMARY KEY,
GEOMETRY MDSYS.SDO_GEOMETRY,
...
There are many X_OBJECT tables. Unfortunately the schema designers (in their infinite wisdom) didn't see any crossover between the various object types. I am unable to change these tables without creating much more work.
For each object table there is a trigger that creates the relevant SDO_GEOMETRY value BEFORE insert or update ( GEOMETRYID is unique - it comes from a sequence ). At the moment the trigger calls a package function which inserts the OBJGEOMETRY record and returns the geometryid.
The problem is that if the parent record is deleted I would like the OBJGEOMETRY child record to be also deleted.
Initially I thought this could be done with Foreign Keys cascade delete, but of course the FK requires a Primary Key in the parent table - obviously this won't work.
However, I discovered that actually a FK requires a unique constraint in the parent table. I can make X_OBJECT.GEOMETRYID unique but then I'm finding issues because that GEOMETRYID isn't yet populated in the parent table but the FK requires it exists. I cannot do that inside the trigger ( by setting :NEW.GEOMETRYID ) so do I have to write the GEOMETRYID first and then commit? I'm not sure and this has bad code smell.
So am I wrong? Is this a more suitable case for a delete trigger? or is there something I'm missing.
Thanks.

If you insert both the OBJGEOMETRY and the X_OBJECT rows in the same transaction, then you can set the FK to DEFERRABLE INITIALLY DEFERRED.
In that case it will be evaluated at COMMIT time, not when you run the INSERT statement.

The triggers should fire before insert or update, not after. Then you can set :NEW.GEOMETRYID with the value returned by your package.
Besides, the foreign keys point the wrong way. It should be
ALTER TABLE x_OBJECT ADD FOREIGN KEY (geometryid) REFERENCES objgeometry(geometryid);
Therefore, you'll need a delete trigger...

Related

How i can solve ORACLE problem in foreign key

I have a problem in oracle and I need help. I have the following query:
1 CREATE TABLE TEST1 (
2 NAME VARCHAR(20)
3 ID VAR(9)
4 PRIMARY KEY(ID)
5 FOREIGN KEY(NAME) References TEST2(ANAME)
6 ON DELETE CASCADE ON UPDATE SET NULL );
If I want to delete line #6 what should i do?
"How I can change the value of primary key and based of that the foreign keys of this pk will change too?"
First, you should never need to do that. Primary keys like this are really just numbers that identify a row, they have no meaning in themselves. It's like asking how you would change the ROWID of a row.
If you must, you could:
Find the foreign keys pointing to this table and disable them with ALTER CONSTRAINT myconstraint DISABLE
Update your primary table and catch the new id value with UPDATE test1 SET id = mysequence.NEXTVAL WHERE id = :oldid RETURNING id INTO :newid, assuming it's set by a sequence.
Update the ids in your other tables with the new id.
Reenable your constraints.
Note that altering constraints is DDL and will do an implicit commit and this approach will leave your tables unprotected by the foreign key constraints.
A second approach would be to:
Insert a new row in the primary table and catch the new id.
Update the id in the foreign tables with the new id.
Delete the old row in the primary table.
Now that I think about it, that second approach seems better to me. No DDL and it just seems cleaner.

Replacing Primary Key PL SQL

I am trying to write a PL/SQL procedure that will look for an existing primary key "supplier_id" from the supplier table and replacing it with a new one. The primary key "supplier_id" is also a foreign key for a few other tables. Therefore I need update the foreign key locations as well. Here is the procedure I have written to solve this:
create or replace PROCEDURE ex5b_supplier_update(supplier_id_delete IN VARCHAR2,
supplier_id_update IN VARCHAR2) IS
CURSOR supplier_cursor IS
SELECT supplier_id
FROM supplier;
supplier_row supplier_cursor%rowtype;
BEGIN
OPEN supplier_cursor;
LOOP
FETCH supplier_cursor INTO supplier_row;
EXIT WHEN supplier_cursor%notfound;
IF ex5b_supplier_exist(supplier_id_delete) THEN
UPDATE supplier
SET supplier_id = supplier_id_update
WHERE supplier_id = supplier_id_delete;
UPDATE PURCHASE_ORDER
SET supplier_id = supplier_id_update
WHERE supplier_id = supplier_id_delete;
UPDATE PRODUCT
SET supplier_id = supplier_id_update
WHERE supplier_id = supplier_id_delete;
DBMS_OUTPUT.PUT_LINE('UPDATED');
ELSE
DBMS_OUTPUT.PUT_LINE('NOT UPDATED');
END IF;
END LOOP;
CLOSE supplier_cursor;
END;
The procedure gives me the following error:
Error starting at line : 2 in command -
BEGIN
ex5b_supplier_update('S500','S600');
END;
Error report - ORA-02292: integrity constraint (SYSTEM.PRODUCT_FK)
violated - child record found ORA-06512: at
"SYSTEM.EX5B_SUPPLIER_UPDATE", line 15 ORA-06512: at line 2
02292. 00000 - "integrity constraint (%s.%s) violated - child record found"
*Cause: attempted to delete a parent key value that had a foreign
dependency.
*Action: delete dependencies first then parent or disable constraint.
Which makes total sense you cannot delete a primary key that is used as a foreign key. But I also can't change foreign keys that have no primary keys.
So my question is how can I change the supplier_id and all its foreign keys at the same time to avoid this error?
In a relational database a primary key is guaranteed to be three things:
1) Not nullable
2) Unique
3) UNCHANGING
It's the third rule which you're violating here, and from the errors you're getting perhaps you see why. This way lies madness. Do not change the value of a primary key. Change the attribute values all you like, so that the row now appears to be something completely different - but do not change the primary key. If you need think you need to change the primary key what you're really saying is that your primary key is not, in fact, primary. It might be a unique key, but it is by definition not a primary key.
Primary keys do not get changed.
Best of luck.
EDIT
If you really want to "change" the primary key without disabling constraints and etc, here's what you do:
Start a transaction.
Create a new row in your table with a new ID.
Copy all attributes EXCEPT FOR THE PRIMARY KEY ID COLUMN from the "original" row to the "new" row.
Update all rows in tables with foreign key constraints which reference the "original" row to reference the "new" row, i.e. change the "old" ID value to the "new" ID value.
Delete the "original" row.
COMMIT the transaction.
When done in this manner you don't violate any of the rules regarding primary keys, and at the end of the transaction the primary key appears to have been changed and all FK's are updated.
Best of luck.

Oracle Partial Foreign Key that is not unique

I have some problems with Oracle Foreign Composite keys. I have an application that is somewhat big (you know, 5000+ tables, that kind of thing) and we store some 'static' (it actually can change) data into some set of tables. This data is referenced by a lot of the tables through the database, so it worked like this:
TABLE StaticData
ID(PK) Data
1 StaticData1
2 StaticData2
...
n StaticDataN
TABLE TypicalTable
ID(PK) StaticDataID(FK to StaticData)
1 1
2 1
3 7
4 2
...
n n
And all was well in Wonderland.
But some changes of spec, and some meetings with the client after, we were tasked with having different 'versions' of the data ready to replace the static data when some time arrives. Last part was easy, we can create jobs that will check every day/week for a date and change the data, but we will have to maintain older and newer versions of the data... in the same table. So now StaticData looks like:
TABLE StaticData
ID(PK) Data KickInDate(Also PK)
1 StaticData1.1 01/01/1900
1 StaticData1.2 10/07/2014
1 StaticData1.3 12/12/2015
2 StaticData2.1 01/01/1900
...
n StaticDataN.1 01/01/1900
And of course all integrity reference has gone off the board. And of course, since I cannot put a UNIQUE constraint in the ID, I cannot keep the foreign keys.
I have searched the net for a solution for this (less restrictive kind of foreign keys) and most of the time the solution is to use triggers checking BEFORE INSERT|UPDATE|DELETE
But that will be kind of a very, very, very big job.
So I ask, Do I have other solutions?
Is there any way to tell Oracle to reference another column of another table even thought is not UNIQUE? (it will definitely be NOT NULL).
Primary keys in Oracle can have duplicates. Primary keys can be built with non-unique indexes and existing values can be excluded by creating the constraint with NOVALIDATE. It is a rarely used feature, will confuse people, and is not a clean solution. But in the real world sometimes data isn't clean and there's no time for the perfect solution.
Sample schema and data.
create table staticData
(
id number not null,
data varchar2(100),
constraint staticData_pk primary key (id)
);
create table typicalTable
(
id number not null,
staticDataID number,
constraint typicalTable_pk primary key (id),
constraint typicalTable_fk foreign key (staticDataID)
references staticData(id)
);
insert into staticData values (1, 'StaticData1');
insert into staticData values (2, 'StaticData2');
insert into typicalTable values(1, 1);
insert into typicalTable values(2, 1);
Process to drop constraints, add duplicate data, and re-enable constraints.
--Drop constraints.
alter table typicalTable drop constraint typicalTable_fk;
alter table staticData drop constraint staticData_pk;
--Add semi-duplicate data.
insert into staticData values (1, 'StaticData1.2');
--Use a non-unique index to build a NOVALIDATE primary key.
create index staticData_pk on staticData(id);
alter table staticData add constraint staticData_pk primary key (id) novalidate;
alter table typicalTable add constraint typicalTable_fk foreign key(staticDataID)
references staticData(id);
No, the target column(s) must be unique, that's the whole idea. However, you can propagate an additional version column from StaticData to TypicalTable:
CREATE TABLE StaticData (
id NUMBER,
version NUMBER,
col1 ... coln,
PRIMARY KEY (id,version)
);
CREATE TABLE TypicalTable (
StaticDataID NUMBER,
version NUMBER,
colx ... coly,
FOREIGN KEY (StaticDataID, version) REFERENCES StaticData(id, version)
);

Bidirectional Foreign Keys Design

Say there are two tables, Company and Employee. Employee has a foreign key to Company and Company has a foreign key to Employee. How should I insert and delete data into these tables without getting referential integrity errors?
COMPANIES
ID
NAME
CONTACT_EMPLOYEE_ID --FK
EMPLOYEES
ID
NAME
COMPANY_ID --FK
I imagine this is a fairly common problem. I have researched it but have been unable to find much information. Perhaps the problem comes under a more common name I am not aware of.
There are several methods available:
Is the CONTACT_EMPLOYEE_ID column nullable? If it is, just insert company, insert employee and then update the company record.
You could also set one of the constraints as deferrable. You could then set the constraint as deferred, insert both records and then commit.
There are generally 2 strategies:
Leave one of the FKs NULL-able (and then insert NULL into that table, insert row into other table and finally update the NULL).
Defer one of the FKs.
You could even leave both FKs NULL-able or deferrable (or even a combination of the two), so you can perform the insertion in both directions.
You could also consider placing all the EMPLOYEES fields into COMPANIES.
Apart from the other suggestions already made, which are good (make one of the FK columns NULLable, or make the FK constraint deferrable), another one is to make the NOT NULL constraint deferrable, e.g.:
create table COMPANIES (
ID number not null,
NAME varchar2(100) not null,
CONTACT_EMPLOYEE_ID number,
constraint contact_not_null
check (CONTACT_EMPLOYEE_ID not null)
deferrable
initially deferred
);
Now, you can insert a row with NULL for the employee id, insert the employee, then update companies.contact_employee_id with the new employee ID, then COMMIT.

does foreign key always reference to a unique key in another table?

Is it not possible that foreign key(single column) in a child table references to a parent key which has some duplicate values?
By the SQL standard, a foreign key must reference either the primary key or a unique key of the parent table. If the primary key has multiple columns, the foreign key must have the same number and order of columns. Therefore the foreign key references a unique row in the parent table; there can be no duplicates.
Re your comment:
If T.A is a primary key, then no you can't have any duplicates. Any primary key must be unique and non-null. Therefore if the child table has a foreign key referencing the parent's primary key, it must match a non-null, unique value, and therefore references exactly one row in the parent table. In this case you can't make a child row that references multiple parent rows.
You can create a child row whose foreign key column is NULL, in which case it references no row in the parent table.
No, it is not possible.
When you define a foreign key constraint on a table, it means there is only one corresponding key on the foreign table. If multiples existed on the foreign table which one would be meant?
Wikipedia has this definition on the Foreign key entry:
A foreign key is a field in a relational table that matches a candidate key of another table
Candidate keys are unique within a table.
Yes, it is possible for a foreign key to reference a column with duplicate values.
This can happen if the primary key uses a non-unique index and is not validated when it is created. (But I have never seen a situation like this in real life. As #Bill Karwin pointed out, it would be very confusing. So this may not be a situation you really need to worry about.)
--Create a table with two duplicate rows
create table test1(a number);
insert into test1 values(1);
insert into test1 values(1);
commit;
--Create a non-unique index
create index test1_index on test1(a);
--Use the non-unique index for the primary key, do not validate
alter table test1 add constraint test1_pk primary key (a)
using index test1_index novalidate;
--Build another table with a foreign key to TABLE1
create table test2(a number,
constraint test2_fk foreign key (a) references test1(a));
--Inserting a value that refers to the duplicate value still works.
insert into test2 values(1);
commit;
--The foreign key still works:
--ORA-02291: integrity constraint (TEST2_FK) violated - parent key not found
insert into test2 values(2);
--The primary key works as expected, but only for new values:
--ORA-00001: unique constraint (TEST1_PK) violated
insert into test1 values(1);

Resources