Bidirectional Foreign Keys Design - oracle

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.

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.

FK on a single column referencing a column from composite PK

Not able to create /find the logic to apply FK on a column in child table referencing a column from composite PK of parent table.
create table product(prod_id number,
prod_name varchar2(20),
price number,
constraint PK12 primary key(prod_id,prod_name));
Table created.
create table purchase(prod_id number,
purchase_price number,
constraint FK12 foreign key(prod_id) references product(prod_id));
create table purchase(prod_id number,
purchase_price number,
constraint FK12 foreign key(prod_id) references product(prod_id))
ERROR at line 1:
ORA-02270: no matching unique or primary key for this column-list
Kinldy suggest how i can incorporate this logic.
Thanks.
You can't.
As the error says there's no matching primary key for that column list; you must have one. You have three options:
Remove PROD_NAME from the primary key of PRODUCT. On the face of it this seems like the logical solution, if this is not required in order to make the primary key unique.
Add PROD_NAME to the PURCHASE table.
Create a unique index on PURCHASE.PROD_ID. This seems excessive if it would be a primary key candidate anyway.
I suspect that this is not unique to Oracle. Considering you have a composite primary key in the referenced table, that implies that only one of the columns comprising the composite key is not enough to uniquely identify the record in that table. Therefore, it's impossible to reference only a single column of the primary key in a foreign key relationship that's one-to-many (e.g. one record in the referenced table can have many records in the referencing table--the one with the FK). However, if the relationship to be established is many-to-many, this may be possible.
HTH.

Foreign Keys or Delete Trigger?

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...

Oracle composite key deletion very slow

I have one table with a composite key (REGION) and another table (CITY) that references that table. Inserts, queries, and individual deletions work quickly. The problem is that when I try to bulk-delete the contents of CITY using sqlplus, delete from CITY, it takes forever. This table will have ~400,000 entries and it takes 15-20 minutes just to delete 50,000 entries. Here is my setup using Oracle 11:
create table COUNTRY
(
id varchar2(32) NOT NULL -- PK
...
);
create table REGION -- about 4000 entries
(
country varchar2(32) NOT NULL -- PK, FK to COUNTRY
regionCode char(2) NOT NULL -- PK
...
);
create table CITY -- about 400,000 entries
(
id number NOT NULL -- PK
country varchar2(32) NOT NULL -- FK to COUNTRY
regionCountry varchar2(32) NULL -- FK to REGION
regionCode char(2) NULL -- FK to REGION
...
);
create table LOCATION -- about 2,500,000 entries
(
id varchar2(32) NOT NULL -- PK
country varchar2(32) NOT NULL -- FK to COUNTRY
city number NULL -- FK to CITY
...
);
ALTER TABLE COUNTRY ADD CONSTRAINT PK_COUNTRY PRIMARY KEY (id) USING INDEX;
ALTER TABLE REGION ADD CONSTRAINT PK_REGION PRIMARY KEY (country, regionCode) USING INDEX;
ALTER TABLE CITY ADD CONSTRAINT PK_CITY PRIMARY KEY (id) USING INDEX;
ALTER TABLE IPGeoLoc ADD CONSTRAINT PK_LOCATION PRIMARY KEY (id) USING INDEX;
ALTER TABLE REGION ADD CONSTRAINT FK_REGION_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE CITY ADD CONSTRAINT FK_CITY_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE CITY ADD CONSTRAINT FK_CITY_REGION
FOREIGN KEY (regionCountry, regionCode) REFERENCES REGION (country, regionCode);
ALTER TABLE LOCATION ADD CONSTRAINT FK_LOCATION_COUNTRY
FOREIGN KEY (country) REFERENCES COUNTRY (id);
ALTER TABLE LOCATION ADD CONSTRAINT FK_LOCATION_CITY
FOREIGN KEY (city) REFERENCES CITY (id);
The varchar2(32) fields are GUIDs. I know I should not use GUIDs as a PK but I cannot change that unless I have proof that this is the problem.
I can bulk-delete entries from LOCATION with no problem, 300,000 in a couple of seconds, so this leads me to believe it is the composite key that is giving me trouble.
The secondary issue is that I currently have two country columns in the CITY table - one linked directly to COUNTRY and the other linked as part of the composite key to REGION. I know how I would do this in code and only have one country column but I have to use Hibernate. This works the way it is except for the delete problem so I can't change it unless I can prove this is causing an issue. I'm using sqlplus to try the deletions so I know Hibernate is not causing the delete problem.
My wager is that the problem has nothing to do with the presence of a composite key and everything to do with an unindexed foreign key.
Unless you've omitted it from your question, the CITY column in the LOCATION table is not indexed. That means that every time you try to delete a row from CITY, Oracle has to do a full table scan on the LOCATION table looking for rows in LOCATION that would be orphaned in order to enforce the foreign key constraint. In general, if you want to ever delete from the parent, the foreign key in the child table needs to be indexed. So LOCATION should have indexes on both CITY and COUNTRY, the CITY table should have indexes on COUNTRY and (regionCountry, regionCode), etc.
Even if all the rows from LOCATION have been deleted, if Oracle has to do a full table scan on LOCATION, it has to read up to the high water mark of the table. If the table previously had 2.5 million rows and you just did a DELETE, you would still have to read however many blocks were required to store those 2.5 million rows every time you delete a row from CITY.
You can test whether my hunch is correct in a few different ways
You can index the CITY column in the LOCATION table.
You can drop the foreign key constraint on LOCATION that references the CITY table.
You can truncate the LOCATION table instead of deleting the rows so that the high water mark gets reset and a table scan will take much less time.

How to have unique primary key in two tables?

I have two tables in my system EMPLOYEE and EMPLOYEE_FORECAST. Both have the same columns, entire structure is same. I have another archive table with same structure called EMPLOYEE_ARCHIVE table.
I need to put data from both tables to this archive table. Since records in EMPLOYEE and EMPLOYEE_FORECAST may have same primary key e.g. a record in EMPLOYEE will have a pk of say 100 and another record in EMPLOYEE_FORECAST may also have pk of 100 and this will definitely happen so when they are inserted into archive table I will have a duplicate primary key.
The problem is I will also have some relation table like employee_products, employee_forecast_products and also employee_archive_products. These tables will have emp_id and product_id. So with same emp_id I wont be able to figure out the exact employee.
So, is there any way to have a unique primary key both the EMPLOYEE and EMPLOYEE_FORECAST tables.
So you cannot not use the PK column of EMPLOYEE table as a PK column of the archive table.
You can add a new PK column to the archive table.
If, for some reason, you want the EMPLOYEE table's PK column to be the PK in the archive table, then you could add a flag column to the archive table which would indicate from which table the record comes from. And you could have a composite PK in the archive table containing the original PK and the flag column. (In general, I discourage composite PK-s, so, even if you want to have this flag column, you could have an additional normal PK column in the archive as well.)
To expand on my comment, set up you archive table as:
EMPLOYEE
EMPID NUMBER PK
EMPNAME VARCHAR2(30)
...
EMPLOYEE_FORECAST
EMPID NUMBER PK
EMPNAME VARCHAR2(30)
...
EMPLOYEE_ARCHIVE
ORIG_TABLE VARCHAR2(30) PK
EMPID NUMBER PK
EMPNAME VARCHAR2(30)
...
Data in EMPLOYEE_ARCHIVE:
ORIG_TABLE EMPID EMPNAME
------------------------------------
EMPLOYEE 100 JO BLOGGS
EMPLOYEE_FORECAST 100 JO BLOGGS
As the archive table PK is across both the original table and empid columns it will remain unique for all your data.
Obviously this is just an example and you can use whatever derived column you want to enforce the uniqueness in your archive table.
Hope it helps...
Create a Common Super-Table. Make another table EMPLOYEE_ID with only a primary key. Let both EMPLOYEE, EMPLOYEE_FORECAST and EMPLOYEE_ARCHIVE reference it.
Your data model seems a tad confused. If EMPLOYEE and EMPLOYEE_FORECAST have identical structures why have two tables? What is the business rule here?
And if they are supposed to be two separate tables why store them in a common archive table? Why not have separate archives for each table?
I agree with #Ollie. You need to rethink your data model so it clearly expresses how your business operates. If you post your business rules here I'm sure we can help you untangle things. But here is probably the crucial question: do the following keys identify one employee (i.e one person in the real world) or two?
employee.emp_id = 100
employee_forecast.emp_id = 100

Resources