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 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?
This is a particular problem that I have come across many times,
but I have never really found a simple solution to this (seemingly) simple problem.
How to you ensure that a given parent has a fixed number of children?
1) Example.
How do you make sure a given class has only , say, 50 students enrolled..?
create table class(
class_id number primary key,
class_name varchar2(50),
class_attributes varchar2(50)
);
create table student(
student_id number primary key,
student_name varchar2(50),
student_attributes varchar2(50)
);
create table class_student_asc(
class_id number,
student_id number,
other_attributes varchar2(50),
constraint pk_class_student_asc primary key (class_id,student_id),
constraint fk_class_id foreign key (class_id) references class(class_id),
constraint fk_student_id foreign key (student_id) references student(student_id)
);
These are the implementations that I know of.
Let me know which one you'd prefer and if there is a simpler way to achieve this.
a)
Implementing it with triggers on the child table (class_student_asc).
Querying the same table in a before insert, update trigger to get the count.
Since this gives the mutating table error, this is split into two different statement-level
triggers (before-statement and after-statement) to achieve the result..
http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
b)
Include a count variable in the class table and lock the parent record for update before inserting a record ito the child table.
So, something like..
create table class(
class_id number primary key,
class_name varchar2(50),
class_attributes varchar2(50),
class_count INTEGER,
constraint chk_count_Students check (class_count <=5)
);
and instead of exposing the table class_student_asc for inserts and so on...
write a procedure and then use it in all applications..
procedure assign_new_student(
i_student_id number,
i_class_id number)
is
begin
select class_count
from class
where class_id = i_class_id
for update ; -- or for update nowait, if you want the other concurrent transaction to fail..
insert into class_student_asc(
class_id, student_id)
values (i_class_id,i_student_id);
update class
set class_count = class_count + 1
where class_id = i_class_id;
commit;
end assign_new_student;
c)
There are, of course, cases like a user having two email adresses.
In such a scenario, the email address itself does not have any attribute
and the table could be as simple as
create table user_table
(
user_id number,
user_name varchar2(50),
user_email_primary varchar2(50),
user_email_secondary varchar2(50)
);
However, we cannot extend the same approach for the question above.....as the number of columns and the constraint checks would slow down the inserts and updates . Also, this would mean we'd need a new column added everytime we change the rule.. too.
Please advice.
For Oracle consider this approach.
Create a materialized view summarising the number of students per class. Have the mview refresh on commit and add a constraint to the mview that prohibits a count of more than 50 students per class.
This code demonstrates how to use a fast refresh on commit mview to enforce the student count limit,
insert into class(class_id, class_name) values (1, 'Constraints 101');
insert into class(class_id, class_name) values (2, 'Constraints 201');
insert into student(student_id, student_name) values(1, 'Alice');
insert into student(student_id, student_name) values(2, 'Bob');
insert into student(student_id, student_name) values(3, 'Carlos');
create materialized view log on class_student_asc with primary key, rowid, sequence including new values;
create materialized view class_limit refresh fast on commit as
select class_id, count(*) count from class_student_asc group by class_id;
alter table class_limit add constraint class_limit_max check(count <= 2);
insert into class_student_asc(class_id, student_id) values(1, 1);
insert into class_student_asc(class_id, student_id) values(1, 2);
insert into class_student_asc(class_id, student_id) values(1, 3);
The constraint will be violated when the transaction is committed, not when the third student is added to the class. This might make a difference to your application code. SQL Developer fails to display the error but sql*plus does display it.
I can think of a couple of ways:
1. Triggers
Have an INSERT Trigger on the table that checks on INSERT and does the validation for you.
2. One-to-One relationships
Let's say you want one parent to have only two children. Create two one-to-one relationships.
Another question had a similar requirement, which you can constrain using a combination of a CHECK constraint with a UNIQUE constraint on a "count" column:
How to fastly select data from Oracle
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