I've been racking my brain trying to come up with a solution to this.
For a database class, I need to implement the following:
Table HUSBANDS: (Name Varchar2(10)) (Wife Varchar2(10))
Table WIVES: (Name Varchar2(10)) (Husband Varchar2(10))
and using Oracle constraints, enfore the following rules:
No two husbands can have the same name
No two wives can have the same name
Every wife must have one and only one husband
Every husband must have one and only one wife
So far, I have implemented the table in Oracle SQL:
create table husbands(
name varchar2(10) not null
, wife varchar2(10) not null
);
create table wives(
name varchar2(10) not null
, husband varchar2(10) not null
);
I'm pretty sure I have solved points 1 and 2 using correct primary keys:
alter table husbands
add constraint husbands_pk
primary key(name);
alter table wives
add constraint wives_pk
primary key(name);
And here is where I'm running into issues. I figured to use foreign keys to implement steps 3 and 4:
alter table husbands
add constraint husbands_fk_wife
foreign key(wife)
references wives(name);
alter table wives
add constraint wives_fk_husband
foreign key(husband)
references husbands(name);
Now the test case my professor is using is to be able to add a married couple to the database. The problem I am having is how to do this using only constraints. If I wanted to add Jack and Jill as a married couple, one cannot add the husband until the wife is added. the wife cannot be added until a husband is added.
I think my problem is using foreign keys. A check constraint might work in this situation, but I cannot conceptualize how it would work.
An alternative to deferrable constraints is a third table of (husband, wife) with two unique constraints (one on husband, one on wife), and have referential integrity constraints between that and the husbands table and wifes table. The wife/husband columns on the husbands/wifes tables would be redundant and should be dropped.
PS. Should it be WIVES rather than WIFES ?
The need to use deferrable constraints is often a pointer to design problems. Certainly this data model is not a good one: it is not properly normalised. A normalised solution would look like this:
PERSON
------
ID number
NAME varchar2(30)
PRIMARY KEY (ID)
MARRIED_COUPLE
--------------
PARTNER_1 number
PARTNER_2 number
PRIMARY KEY (PARTNER_1, PARTNER_2)
FOREIGN KEY (PARTNER_1) REFERENCES (PERSON.ID)
FOREIGN KEY (PARTNER_2) REFERENCES (PERSON.ID)
This has the added advantage of supporting civil partnerships :) If you want to discourage bigamy then you could put unique keys on PARTNER_1 or PARTNER_2.
It is trickier to model cultures where polygyny or polyandry is permitted.
edit
What David is objecting to (in the comments) is this:
SQL> create table married_couple (partner_1 number, partner_2 number)
2 /
Table created.
SQL> alter table married_couple add primary key (partner_1, partner_2)
2 /
Table altered.
SQL> insert into married_couple values (1, 2)
2 /
1 row created.
SQL> insert into married_couple values (2,1)
2 /
1 row created.
SQL>
It's a valid point but it is resolvable. For instance, with Oracle I can create a unique function-based to enforce uniqueness of permutations.
SQL> delete from married_couple
2 /
2 rows deleted.
SQL> create unique index mc_uidx on married_couple
2 (greatest(partner_1, partner_2),least(partner_1, partner_2))
3 /
Index created.
SQL> insert into married_couple values (1, 2)
2 /
1 row created.
SQL> insert into married_couple values (2,1)
2 /
insert into married_couple values (2,1)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.MC_UIDX) violated
SQL>
To avoid polygamy we can use a similar trick. We don't want this:
SQL> insert into married_couple values (1,3)
2 /
1 row created.
So, we need two indexes:
SQL> delete from married_couple where partner_2 = 3;
1 row deleted.
SQL> create unique index mc1_uidx
2 on married_couple (greatest(partner_1, partner_2))
3 /
Index created.
SQL> create unique index mc2_uidx
2 on married_couple (least(partner_1, partner_2))
3 /
Index created.
SQL> insert into married_couple values (3, 1)
2 /
insert into married_couple values (3, 1)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.MC2_UIDX) violated
SQL>
To those who think it's cheating to solve a data modelling issue with an implementation trick, I plead "Guilty as charged" but I have had a long and trying day of it.
Study deferrable constraints (not a new type, just a param to the existing ones), so far you did good.
Deferrable constraints are the right way to do it. Interestingly, there is an alternative way however -- with your setup and Oracle 10gR2:
SQL> CREATE OR REPLACE TRIGGER husband_wife_trg AFTER INSERT ON husbands
2 FOR EACH ROW
3 BEGIN
4 INSERT INTO wives VALUES (:new.wife, :new.name);
5 END;
6 /
Trigger created
SQL> INSERT INTO husbands VALUES ('Husband A', 'Wife B');
1 row inserted
SQL> SELECT * FROM wives;
NAME HUSBAND
---------- ----------
Wife B Husband A
I don't like putting transactional logic into triggers, but if you follow this path you don't need deferrable constraints.
Silly idea - why not just have a single table "Couples" with columns "Husband_Name" and "Wife_Name" that each have a unique constraint? Seems to me like this satisfies all the requirements. :)
1)setAutoCommit() as false
2)Inserts record into both table in one Unit Of Work.
3)commit
You need a third table, not only for fixing this, but also for properly handling polygamy/bigamy which is legal in over 40 countries of the world.
Sorry - most answers are not adressing the exact problem at hand:
"MUST HAVE ONE AND ONLY ONE"
This essentially implies: YOU CAN NOT INSERT A SINGLE PERSON into the Database!!!
*Because a single Person would not have exactly one partner!
So the only valid solutions are:
Deferrable Constraints: Easy as it can be - just mark your Constraints deferrable and then insert a wife and a husband and it will only check for integrity after commit (I don't know what people are complaining about - this is not cheating or strange... It is common practice and the only way in many commercial cases!!!)
INSERT ALL - at least with newer Oracle Versions you can use the INSERT ALL Statement, which will insert into multiple tables at once. So you can write a single "Insert wife and hsuband" which is a pissibility for many use-cases.
Trigger: In this special case a Trigger would do the trick - but as soon as you have additional attributes it wouldn't work anymore...
But all other answers were simply wrong for the proposed problem: Two objects with a mandatory 1 to 1 connection
Related
I have a table with 3 columns:
ID, PARENT_ID, NAME
PARENT_ID has a foreign key relationship with ID in the same table. This table is modeling a hierarchy.
Sometimes the ID of a record will change. I want to be able to update a record's ID, then update the dependent records' PARENT_ID to point to the new ID.
The problem is, when I attempt to update the ID of a record it breaks the integrity and fails immediately.
I realize I could insert a new record with the new ID, then update the children, then delete the old record, but we have a lot of triggers in place that would get screwed up if I did that.
Is there any way to temporarily update the parent with the promise of updating the children (obviously it would fail on commit) without disabling the foreign key briefly?
What you want is a 'deferred constraint'.
You can pick between the two types of deferrable constraints, 'INITIALLY IMMEDIATE' and 'INITIALLY DEFERRED' to drive default behavior - whether the database should default to check the constraint after every statement, or if it should default to only checking constraints at the end of the transaction.
Answered slower than Chi, but felt it would be nice to include code sample, so that the answer could be found on SO.
As Chi answered, deferrable constraints make this possible.
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID) deferrable initially immediate);
Table created.
SQL> insert into T values (1, null, 'Big Boss');
1 row created.
SQL> insert into T values (2, 1, 'Worker Bee');
1 row created.
SQL> commit;
Commit complete.
SQL> -- Since initially immediate, the following statement will fail:
SQL> update T
2 set ID = 1000
3 where ID = 1;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint (S.T_HIREARCHY_FK) violated - child record found
SQL> set constraints all deferred;
Constraint set.
SQL> update T
2 set ID = 1000
3 where ID = 1;
1 row updated.
SQL> update T
2 set parent_ID = 1000
3 where parent_ID = 1;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from T;
ID PARENT_ID NAME
---------- ---------- ----------------------------------------
1000 Big Boss
2 1000 Worker Bee
SQL> -- set constraints all deferred during that transaction
SQL> -- and the transaction has commited, the next
SQL> -- statement will fail
SQL> update T
2 set ID = 1
3 where ID = 1000;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint S.T_HIREARCHY_FK) violated - child record found
I believe, but could not find the reference, that deferrability is defined at constraint creation time and can not be modified later. The default is non-deferrable. To change to deferrable constraints you'll need to do a one time drop and add constraint. (Properly scheduled, controlled, etc.)
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID));
Table created.
SQL> alter table T drop constraint T_HIREARCHY_FK;
Table altered.
SQL> alter table T add constraint T_HIREARCHY_FK foreign key (parent_ID)
2 references T(ID) deferrable initially deferred;
Table altered.
The common advice with scenarios like this is to employ deferrable constraints. However, I think these situations are almost always a failure of application logic or data model. For instance, inserting a child record and a parent record in the same transaction can be a problem if we execute it as two statements:
My test data:
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
111 parent 2
210 110 child 0
220 111 child 1
221 111 child 2
222 111 child 3
6 rows selected.
SQL>
The wrong way to do things:
SQL> insert into t23 (id, parent_id, name) values (444, 333, 'new child')
2 /
insert into t23 (id, parent_id, name) values (444, 333, 'new child')
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL> insert into t23 (id, parent_id, name) values (333, null, 'new parent')
2 /
1 row created.
SQL>
However, Oracle supports a multi-table INSERT synatx which allows us to insert the parent and child records in the same statement, thus obviating the need for deferrable constraints:
SQL> rollback
2 /
Rollback complete.
SQL> insert all
2 into t23 (id, parent_id, name)
3 values (child_id, parent_id, child_name)
4 into t23 (id, name)
5 values (parent_id, parent_name)
6 select 333 as parent_id
7 , 'new parent' as parent_name
8 , 444 as child_id
9 , 'new child' as child_name
10 from dual
11 /
2 rows created.
SQL>
The situation you are in is similar: you want to update the primary key of the parent record but can't because of the existence of the child records: And you can't update the child records because there is no parent key. Catch-22:
SQL> update t23
2 set id = 555
3 where id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.T23_T23_FK) violated - child record found
SQL> update t23
2 set parent_id = 555
3 where parent_id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL>
Once again the solution is to do it in a single statement:
SQL> update t23
2 set id = decode(id, 111, 555, id)
3 , parent_id = decode(parent_id, 111, 555, parent_id)
4 where id = 111
5 or parent_id = 111
6 /
4 rows updated.
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
210 110 child 0
220 555 child 1
221 555 child 2
222 555 child 3
333 new parent
444 333 new child
555 parent 2
8 rows selected.
SQL>
The syntax in the UPDATE statement is a bit clunky but kludges usually are. The point being that we should not have to update primary key columns very often. Indeed, as immutability is one of the characteristics of "primary key-ness" we shouldn't really have to update them at all. Needing to do so is a failure of the data model. One way of avoiding such failures is to use a synthetic (surrogate) primary key, and simply enforce the uniqueness of the natural (aka business) key with a unique constraint.
So why does Oracle offer deferrable constraints? They are useful when we undertake data migrations or bulk data uploads. They permit us to cleanse data in the database without staging tables. We really shouldn't need them for regular application tasks.
Recommendations to use a surrogate key are excellent, IMO.
More generally, the problem with this table is that it lacks a primary key. Recall that a primary key must be three things:
Unique
Non-null
Unchanging
Databases I'm familiar with enforce (1) and (2), but I don't believe they enforce (3), which is unfortunate. And that's what's kicking you in the butt - if you change your "primary key" you have to chase down all the references to that key field and make equivalent alterations if you don't want to break integrity. The solution, as others have said, is to have a true primary key - one that is unique, non-null, and which doesn't change.
There's reasons for all these little rules. This is a great opportunity to understand the "unchanging" part of the primary key rules.
Share and enjoy.
If this were any other database besides Oracle, you could declare the foreign key with ON UPDATE CASCADE. Then if you change a parent's id, it would propagate the change atomically to the child's parent_id.
Unfortunately, Oracle implements cascading deletes but not cascading updates.
(This answer is for information purposes only, since it doesn't actually solve your problem.)
You need to use a deferrable constraint (see Chi's answer).
Otherwise, in order to add a value that will fail the foreign key constraint, you have to either disable or drop & re-create the foreign key constraint.
Situations like these employ a surrogate key that can be altered by users as necessary, without impacting referential integrity. To expand on this idea, currently the setup is:
ID (pk)
PARENT_ID (foreign key, references ID column -- making it self referential)
..and the business rules are that ID can change. Which is fundamentally bad from a design perspective - primary key are immutable, unique, and can't be null. So the solution to the situation when you're building your data model is to use:
ID (pk)
PARENT_ID (foreign key, references ID column -- making it self referential)
SURROGATE_KEY (unique constraint)
The SURROGATE_KEY is the column that supports change without affecting referential integrity - the parent & child relationship is intact. This means that a user can tweak the surrogate key to their hearts delight without needing deferred constraints, enable/disable or drop/recreate foreign key constraints, ON UPDATE CASCADE...
As a rule, in data modeling you NEVER display primary key values to the user because of situations like these. For example, I have a client who wants their jobs number to change on the start of the year, with the year at the start of the number (IE: 201000001 would be the first job created in 2010). What happens when the client sells the company, and the new owner needs a different scheme for their accounting? Or, what if the numbering can't be maintained while transitioning to a different database vendor?
I have 3 tables:
Shirts(model, color)
Gloves(model, color)
Socks(model, color)
Where model is primary key in all 3 tables
Now I need to make another table:
Products (model, price)
So, in products I need to get all models from my first 3 tables, in one single column. How can I do that?
In my opinion, you've designed it wrong. Suggestion (as a comment under the question, saying that products should be referenced by 3 another tables) is - again, in my opinion - wrong.
You shouldn't create separate tables for shirts, gloves or socks. What will you do when you start selling hats or trousers or shoes? Will you create new tables for all of those? Of course not - those are just clothes (products) types.
So - create one table that contains all types; when new type appears, simply add a new row into that table and reference it from the products table.
Something like this:
SQL> create table product_type
2 (id_type number primary key,
3 name varchar2(30)
4 );
Table created.
SQL> create table products
2 (id_product number primary key,
3 id_type number references product_type,
4 color varchar2(20),
5 price number
6 );
Table created.
SQL> insert into product_type
2 select 1, 'shirt' from dual union all
3 select 2, 'glove' from dual union all
4 select 3, 'socks' from dual;
3 rows created.
SQL> insert into products
2 -- shirt
3 select 100, 1, 'red', 100 from dual union all
4 -- gloves
5 select 101, 2, 'blue', 150 from dual;
2 rows created.
SQL>
Here come the shoes:
SQL> insert into product_type
2 select 4, 'shoes' from dual;
1 row created.
SQL> insert into products
2 select 113, 4, 'brown', 400 from dual;
1 row created.
SQL>
Conclusion: read about normalization.
If someone says that "colors should be in a separate table", well - perhaps, that wouldn't be a bad idea either. Furthermore, does the products table have to be expanded by a date column (which would show what price was valid at certain period)? Not a bad idea either. There are numerous options you can include into the model. I just tried to point you into the right direction.
A foreign key can be created from the Products table to each of the other tables. However, creating a foreign key from each of the other tables (Shirts, Gloves, Socks) is not possible (unless each Product exists in all three tables). A foreign key from Shirts, Gloves, and Socks would basically say: before an insert into the Products table ensure that there is a record in each of the three other tables with the same key.
As suggested by the other posters, it is probably better to consider a slightly different design (e.g. add a type column to the products table).
Also, by convention table names should be singular (Product, Shirt, Glove, Sock).
If you mean in an environment like phpMyAdmin, MySQLWorkbench etc. you can just manually go to each product table 'Shirt','Gloves','Socks' and add a reference/foreign key of the field 'Model' to the the field 'Model' in the table 'Product'. The way to do this depends entirely on the programm you are using. Note that ofcourse you should create the new Table either manually or by SQL code before you do this.
There is no problem for multiple tables to point to same other table via foreign keys.
If you want to write SQL code to this you can use something like this, which is the other way around (use three foreign keys in the table 'Product', one for each other table). Note that the tables Socks,Shirts and Gloves must have already been created.
DROP TABLE IF EXISTS `Product` ;
CREATE TABLE `Product` (
`Color` varchar(8),
`Model` varchar(14),
PRIMARY KEY(`Model`),
FOREIGN KEY('Model') REFERENCES `Socks`(`Model`) ON DELETE CASCADE,
FOREIGN KEY('Model') REFERENCES `Gloves`(`Model`) ON DELETE CASCADE,
FOREIGN KEY('Model') REFERENCES `Shirts`(`Model`) ON DELETE CASCADE
) ;
The way you want is something like this (done three times one for each table). Note that as i said before the table Product must have been already created.
DROP TABLE IF EXISTS `Socks` ;
CREATE TABLE `Socks` (
`Color` varchar(8),
`Model` varchar(14),
PRIMARY KEY(`Model`),
FOREIGN KEY('Model') REFERENCES `Product`(`Model`) ON DELETE CASCADE
);
I have 2 tables:
Empleados(**numEmpl**, nombre, apellido, sexo, telefono, salario, numDept)
Departamentos(**numDept**, nombreDept, numDirect)
In departamentos:
numEmpl is primary key
numDept is foreign key reference to Departamentos(numDept).
In departamentos:
numDept is the primary key
And numDirect is foreign key reference to Empleados (numEmpl)
So there is a circular reference.
First of all I created the Tables:
CREATE TABLE EMPLEADOS(numEmpl primary key, nombre,
apellido, sexo, telefono, salario, numDept)
CREATE TABLE DEPARTAMENTOS(numDept primary key, nombreDept, numDirect)
(i didn't write here each of type is each colum)
Now I create the reference between them:
ALTER TABLE DEPARTAMENTOS
ADD CONSTRAINT FK_DEPT_EMP FOREIGN KEY (numDirect)
REFERENCES EMPLEADOS(numEmpl)
ALTER TABLE EMPLEADOS
ADD CONSTRAINT FK_EMP_DEPT FOREIGN KEY (numDept)
REFERENCES DEPARTAMENTOS(numDept).
It worked, so now I tried to insert some data:
INSERT INTO Empleados(numEmpl, nombre, apellidos, sexo, telefono, salario, numDept)
VALUES (1, 'Pepito', 'Pérez', 'H', '111111111', 20000, 1);
INSERT INTO Departamentos(numDept, nombreDept, numDirect)
VALUES (1, 'Direccion', 1);
But now it throws me an error, telling me I can't introduce data in a circular reference, I tryed to disable the circular reference and insert the data, and then enable it again, it worked but someone told me it isn't the right way and I have to do something special while I'm creating the tables to insert the datas in that way and it will work, but I don't have idea how to do it.
I'm using oracle sql developer by the way.
EDIT: Thanks for the answers, but they didn't worked. First of all I only can have that tables, and when I make the insert it MUST work in that way, without making a parameter null and then update it, I'm sorry I didn't say it before.
So the only way I have to do it, it's allowing the circle reference, but when I try to do it in the way someone said here, it tells me something about a rollback, someone can help?
To allow cyclic references, you need deferrable constraints:
ALTER TABLE DEPARTAMENTOS
ADD CONSTRAINT FK_DEPT_EMP FOREIGN KEY (numDirect)
REFERENCES EMPLEADOS(numEmpl)
DEFERRABLE INITIALLY DEFERRED
;
ALTER TABLE EMPLEADOS
ADD CONSTRAINT FK_EMP_DEPT FOREIGN KEY (numDept)
REFERENCES DEPARTAMENTOS(numDept)
DEFERRABLE INITIALLY DEFERRED
;
Deferrable constraints are checked at transaction end; before commit time a spurious invalid database state is allowed to exist (in the original question: between the two insert statements). But the statements must be inside a transaction, so the statements should be enclosed in BEGIN [WORK]; and COMMIT [WORK];.
Circular references are dangerous, and cause you to need to go back and update your data so it is not in an inconsistent state.
If you are in your planning stages still I urge you to take a look at other options to avoid this, otherwise you may run into a lot of headaches down the road.
http://blogs.msdn.com/b/sqlazure/archive/2010/07/01/10033575.aspx
If you do wish to use them still, then I would suggest setting NULL as an allowed value on the departments table (this allows you to insert a new value with no d), Inserting the employee, and then go back and update with the employee id.
This is happening because you can't create a record in the Department table with a value of 1 for numDirect until you have created a record in the Employees table for that employee (numEmpl=1). And you can't create the employee until you have created his department record. This is solved by making the process three steps instead of just two. To do this you have to be able to create the Department record without the numDirect FK value, or you have to able to create the Employee without the numDept FK value.
Say you decide on the latter. In that case Make NumDept Nullable in table EMPLEADOS :
Alter table EMPLEADOS Alter Column numDept null
Then you can:
First, Add a employee with a null value for numDept
INSERT Empleados(numEmpl, nombre, apellidos,
sexo, telefono, salario, numDept)
VALUES (1, 'Pepito', 'Pérez', 'H', '111111111', 20000, null);
Second, Add employee:
INSERT Departamentos(numDept, nombreDept, numDirect)
VALUES (1, 'Direccion', 1);
And finally, Update value of numDept in Department record.
Update Empleados Set numDept = 1
Where numEmpl = 1
Take the numDirect column out of your departamentos table. That table should simply describe the department. Depending on your business rules, you want a one to many relationship between departamentos and Empleados, which you have, or a many to many relationship between them. If an Empleado can work for more than one departamento, then you want to drop the numDept column from the Empleados table and create another table to set up the many to many relationship.
If you manage to figure out a way to add records with your current design, you will have a bigger problem. Instead of having just one record for each departamento, you will need one for every Empleado in it.
I have a database table that I am using as an interface between systems. I write to it, they read from it. I have a foreign key in the table that references my user table.
Now the interface is getting more complex. I now have another use for the interface table that doesn't reference the user table. I can of course relax the foreign key constraint to user table.
What I want to do is have a constraint state either this foreign key is satisfied OR this other key (in different column) is satisfied. Is this possible?
I have an oracle database.
You can declare both columns to be nullable and create a check constraint that ensures that exactly one of the two columns is not NULL.
For example, if I have separate tables for professors and for lecturers and I wanted to model classes such that a class can either have a professor or a lecturer but not both, I can do something like this
SQL> ed
Wrote file afiedt.buf
1 create table professor(
2 professor_id number primary key
3* )
SQL> /
Table created.
SQL> create table lecturer(
2 lecturer_id number primary key
3 );
Table created.
SQL> create table class(
2 class_id number primary key,
3 lecturer_id number references lecturer( lecturer_id ),
4 professor_id number references professor( professor_id ),
5 check( (lecturer_id is null and professor_id is not null) or
6 (lecturer_id is not null and professor_id is null) )
7 );
Table created.
SQL> insert into professor values( 1 );
1 row created.
SQL> insert into lecturer values( 20 );
1 row created.
SQL> insert into class values( 1, 20, null );
1 row created.
SQL> insert into class values( 2, null, 1 );
1 row created.
SQL> insert into class values( 3, 20, 1 );
insert into class values( 3, 20, 1 )
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.SYS_C0014175) violated
Of course, from a data modeling standpoint, it will frequently be a better idea to create a single INSTRUCTOR table with an INSTRUCTOR_TYPE that can be either LECTURER or PROFESSOR and to create the CLASS table with a non-nullable foreign key to the INSTRUCTOR table.
I would create a different table for the new interface requirements. One difference in constraints just leads to another.
Then I'd think about whether I should create a view, and let the application software read from the view instead of from the base table(s).
No, it isn't possible. You can put together what would amount to a tertiary key table that links out to either one or the other tables, but it is very messy and brittle.
Instead, I would highly suggest you revisit your integration strategy, since it seems that your current one isn't able to flex the way you need it to.
I have a table with 3 columns:
ID, PARENT_ID, NAME
PARENT_ID has a foreign key relationship with ID in the same table. This table is modeling a hierarchy.
Sometimes the ID of a record will change. I want to be able to update a record's ID, then update the dependent records' PARENT_ID to point to the new ID.
The problem is, when I attempt to update the ID of a record it breaks the integrity and fails immediately.
I realize I could insert a new record with the new ID, then update the children, then delete the old record, but we have a lot of triggers in place that would get screwed up if I did that.
Is there any way to temporarily update the parent with the promise of updating the children (obviously it would fail on commit) without disabling the foreign key briefly?
What you want is a 'deferred constraint'.
You can pick between the two types of deferrable constraints, 'INITIALLY IMMEDIATE' and 'INITIALLY DEFERRED' to drive default behavior - whether the database should default to check the constraint after every statement, or if it should default to only checking constraints at the end of the transaction.
Answered slower than Chi, but felt it would be nice to include code sample, so that the answer could be found on SO.
As Chi answered, deferrable constraints make this possible.
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID) deferrable initially immediate);
Table created.
SQL> insert into T values (1, null, 'Big Boss');
1 row created.
SQL> insert into T values (2, 1, 'Worker Bee');
1 row created.
SQL> commit;
Commit complete.
SQL> -- Since initially immediate, the following statement will fail:
SQL> update T
2 set ID = 1000
3 where ID = 1;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint (S.T_HIREARCHY_FK) violated - child record found
SQL> set constraints all deferred;
Constraint set.
SQL> update T
2 set ID = 1000
3 where ID = 1;
1 row updated.
SQL> update T
2 set parent_ID = 1000
3 where parent_ID = 1;
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from T;
ID PARENT_ID NAME
---------- ---------- ----------------------------------------
1000 Big Boss
2 1000 Worker Bee
SQL> -- set constraints all deferred during that transaction
SQL> -- and the transaction has commited, the next
SQL> -- statement will fail
SQL> update T
2 set ID = 1
3 where ID = 1000;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint S.T_HIREARCHY_FK) violated - child record found
I believe, but could not find the reference, that deferrability is defined at constraint creation time and can not be modified later. The default is non-deferrable. To change to deferrable constraints you'll need to do a one time drop and add constraint. (Properly scheduled, controlled, etc.)
SQL> drop table t;
Table dropped.
SQL> create table T (ID number
2 , parent_ID number null
3 , name varchar2(40) not null
4 , constraint T_PK primary key (ID)
5 , constraint T_HIREARCHY_FK foreign key (parent_ID)
6 references T(ID));
Table created.
SQL> alter table T drop constraint T_HIREARCHY_FK;
Table altered.
SQL> alter table T add constraint T_HIREARCHY_FK foreign key (parent_ID)
2 references T(ID) deferrable initially deferred;
Table altered.
The common advice with scenarios like this is to employ deferrable constraints. However, I think these situations are almost always a failure of application logic or data model. For instance, inserting a child record and a parent record in the same transaction can be a problem if we execute it as two statements:
My test data:
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
111 parent 2
210 110 child 0
220 111 child 1
221 111 child 2
222 111 child 3
6 rows selected.
SQL>
The wrong way to do things:
SQL> insert into t23 (id, parent_id, name) values (444, 333, 'new child')
2 /
insert into t23 (id, parent_id, name) values (444, 333, 'new child')
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL> insert into t23 (id, parent_id, name) values (333, null, 'new parent')
2 /
1 row created.
SQL>
However, Oracle supports a multi-table INSERT synatx which allows us to insert the parent and child records in the same statement, thus obviating the need for deferrable constraints:
SQL> rollback
2 /
Rollback complete.
SQL> insert all
2 into t23 (id, parent_id, name)
3 values (child_id, parent_id, child_name)
4 into t23 (id, name)
5 values (parent_id, parent_name)
6 select 333 as parent_id
7 , 'new parent' as parent_name
8 , 444 as child_id
9 , 'new child' as child_name
10 from dual
11 /
2 rows created.
SQL>
The situation you are in is similar: you want to update the primary key of the parent record but can't because of the existence of the child records: And you can't update the child records because there is no parent key. Catch-22:
SQL> update t23
2 set id = 555
3 where id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.T23_T23_FK) violated - child record found
SQL> update t23
2 set parent_id = 555
3 where parent_id = 111
4 /
update t23
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found
SQL>
Once again the solution is to do it in a single statement:
SQL> update t23
2 set id = decode(id, 111, 555, id)
3 , parent_id = decode(parent_id, 111, 555, parent_id)
4 where id = 111
5 or parent_id = 111
6 /
4 rows updated.
SQL> select * from t23 order by id, parent_id
2 /
ID PARENT_ID NAME
---------- ---------- ------------------------------
110 parent 1
210 110 child 0
220 555 child 1
221 555 child 2
222 555 child 3
333 new parent
444 333 new child
555 parent 2
8 rows selected.
SQL>
The syntax in the UPDATE statement is a bit clunky but kludges usually are. The point being that we should not have to update primary key columns very often. Indeed, as immutability is one of the characteristics of "primary key-ness" we shouldn't really have to update them at all. Needing to do so is a failure of the data model. One way of avoiding such failures is to use a synthetic (surrogate) primary key, and simply enforce the uniqueness of the natural (aka business) key with a unique constraint.
So why does Oracle offer deferrable constraints? They are useful when we undertake data migrations or bulk data uploads. They permit us to cleanse data in the database without staging tables. We really shouldn't need them for regular application tasks.
Recommendations to use a surrogate key are excellent, IMO.
More generally, the problem with this table is that it lacks a primary key. Recall that a primary key must be three things:
Unique
Non-null
Unchanging
Databases I'm familiar with enforce (1) and (2), but I don't believe they enforce (3), which is unfortunate. And that's what's kicking you in the butt - if you change your "primary key" you have to chase down all the references to that key field and make equivalent alterations if you don't want to break integrity. The solution, as others have said, is to have a true primary key - one that is unique, non-null, and which doesn't change.
There's reasons for all these little rules. This is a great opportunity to understand the "unchanging" part of the primary key rules.
Share and enjoy.
If this were any other database besides Oracle, you could declare the foreign key with ON UPDATE CASCADE. Then if you change a parent's id, it would propagate the change atomically to the child's parent_id.
Unfortunately, Oracle implements cascading deletes but not cascading updates.
(This answer is for information purposes only, since it doesn't actually solve your problem.)
You need to use a deferrable constraint (see Chi's answer).
Otherwise, in order to add a value that will fail the foreign key constraint, you have to either disable or drop & re-create the foreign key constraint.
Situations like these employ a surrogate key that can be altered by users as necessary, without impacting referential integrity. To expand on this idea, currently the setup is:
ID (pk)
PARENT_ID (foreign key, references ID column -- making it self referential)
..and the business rules are that ID can change. Which is fundamentally bad from a design perspective - primary key are immutable, unique, and can't be null. So the solution to the situation when you're building your data model is to use:
ID (pk)
PARENT_ID (foreign key, references ID column -- making it self referential)
SURROGATE_KEY (unique constraint)
The SURROGATE_KEY is the column that supports change without affecting referential integrity - the parent & child relationship is intact. This means that a user can tweak the surrogate key to their hearts delight without needing deferred constraints, enable/disable or drop/recreate foreign key constraints, ON UPDATE CASCADE...
As a rule, in data modeling you NEVER display primary key values to the user because of situations like these. For example, I have a client who wants their jobs number to change on the start of the year, with the year at the start of the number (IE: 201000001 would be the first job created in 2010). What happens when the client sells the company, and the new owner needs a different scheme for their accounting? Or, what if the numbering can't be maintained while transitioning to a different database vendor?