Foreign Key for either-or column? - sql

Is it possible to have a foreign key that requires either column A or column B to have a value, but not both. And the foreign key for column A matches Table 1 and the foreign key for column B matches Table 2?

A check constraint can handle this. If this is SQL Server, something like this will work:
create table A (Id int not null primary key)
go
create table B (Id int not null primary key)
go
create table C (Id int not null primary key, A_Id int null, B_Id int null)
go
alter table C add constraint FK_C_A
foreign key (A_Id) references A (Id)
go
alter table C add constraint FK_C_B
foreign key (B_Id) references B (Id)
go
alter table C add constraint CK_C_OneIsNotNull
check (A_Id is not null or B_Id is not null)
go
alter table C add constraint CK_C_OneIsNull
check (A_Id is null or B_Id is null)
go

It depends on which database you're working with. If you want a table Foo that has FK relationships to Table1 and to Table2 but only one at a time, then you'll need to set up either some sort of trigger (my links assume SQL Server, but the ideas's the same) or Constraint to enforce your rule that only one column have a value.

it is not necessary that a column have values in it at that time of applying foreign key,but the column name would be same and the data types as well.

Related

Ecto - Foreign key constraint on table A for table B or C, but not both B & C

Let's say that table A can belong to either table B or table C, but not both.
It seems that it is possible to use foreign key constraints for this scenario in SQL Server.
How could this relationship be represented in Elixir's Ecto library?
You can structure this as using a separate column for each foreign key constraint and then guaranteeing that only one is populated:
create table A (
. . .
b_id int,
c_id int,
foreign key (b_id) references b(b_id),
foreign key (c_id) references c(c_id),
check (b_id is null or c_id is null)
)

With a foreign composite key, it possible to reference a column and a fixed value?

Following is my use case:
I have 4 tables:
CREATE TABLE A
(
name character(20) NOT NULL,
id integer NOT NULL,
CONSTRAINT a_pkey PRIMARY KEY (id)
)
CREATE TABLE B
(
name character(20) NOT NULL,
id integer NOT NULL,
CONSTRAINT b_pkey PRIMARY KEY (id)
)
CREATE TABLE C
(
name character(20) NOT NULL,
id integer NOT NULL,
CONSTRAINT c_pkey PRIMARY KEY (id)
)
CREATE TABLE X
(
type character(20) NOT NULL,
other_id integer NOT NULL,
id integer NOT NULL,
CONSTRAINT "X_PK" PRIMARY KEY (id)
)
the "other_id" in the table X can be the "id" of any of the table A, B or C. The "type" column in table X is suppose to be indicating which of the tables A,B or C "id" is stored in in "other_id"
example data from table x:
type other_id id
"A" 1 1
"B" 1 2
"C" 1 3
"A" 2 4
Trying to create composite FKs between table X-A, X-B and X-C as follows using a fixed value
ALTER TABLE x
ADD CONSTRAINT X_A_FK FOREIGN KEY (other_id, type) REFERENCES a (id, 'A') ON DELETE CASCADE
but I get this error (same problem for all the FKs):
ERROR: syntax error at or near "'A'"
My question is, with a foreign composite key, it possible to reference a column and a fixed value? If not What would be a better way of approaching this problem?
I would suggest an alternative design:
CREATE TABLE a (
a_id integer PRIMARY KEY -- pk is NOT NULL automatically
,a text NOT NULL
);
CREATE TABLE b ( ...);
CREATE TABLE c ( ...);
CREATE TABLE x (
x_id integer PRIMARY KEY -- or maybe a serial?
,a_id integer REFERENCES a(a_id) -- can be NULL
,b_id integer REFERENCES b(b_id)
,c_id integer REFERENCES c(c_id)
);
The major point is to have three columns (a_id, b_id, c_id) instead of two in your design (other_id, type). Contrary to what one might think, this needs less disk space, while being cleaner and simpler. You don't need additional constraints or indices to enforce referential integrity.
NULL storage is cheap.
If you want to enforce, that at most one of (a, b, c) can be linked at a time, add a CHECK constraint:
ALTER TABLE x ADD CONSTRAINT x_max1_fk
CHECK (a_id IS NULL AND b_id IS NULL
OR b_id IS NULL AND c_id IS NULL
OR a_id IS NULL AND c_id IS NULL)
Seems lengthy, but is very cheap and simple.
If you want to enforce that exactly one of (a, b, c) must be linked at a time, modify to:
ALTER TABLE x ADD CONSTRAINT x_exactly1_fk
CHECK (a_id IS NULL AND b_id IS NULL AND c_id IS NOT NULL
OR b_id IS NULL AND c_id IS NULL AND a_id IS NOT NULL
OR a_id IS NULL AND c_id IS NULL AND b_id IS NOT NULL)
For more than just a few columns I would use:
ALTER TABLE x ADD CONSTRAINT x_exactly1_fk
CHECK ((a_id IS NULL)::int
+ (b_id IS NULL)::int
+ (c_id IS NULL)::int = 1) -- or <= 1 for the former case
Note how I avoid the column names id and name. Use descriptive names instead, this makes your life easier when joining a couple of tables - which you will have to do a lot with a design like this and which is what you do in a relational database.
And I use text instead of varchar(20). Why?
I would also consider the ON UPDATE CASCADE and possibly ON DELETE CASCADE modifiers for the foreign keys.
Per the error you're getting, it's not possible. But you've at least two options to achieve the same:
You could add a check constraint on type and enforce a foreign key on (other_id) or (other_id, type) instead.
You could add constraint triggers that enforces the pseudo-foreign key directly.

Check if data exists in another table on insert?

Table A
(
Table_A_ID int
)
Table B
(
Table_B_ID int
Value int
)
Say I want to insert data into Table B, where 'Value' would be the same as a Table_A_ID.
How would I make a constraint or check that the data actually exists in the table on insertion?
You probably need to enforce data integrity not only on INSERT into Table B, but also on UPDATE and DELETE in both tables.
Anyway options are:
FOREIGN KEY CONSTRAINT on Table B
TRIGGERs on both tables
As a last resort if for some reason 1 and 2 is not an option STORED PROCEDUREs for all insert, delete update operations for both tables
The preferred way to go in most cases is FOREIGN KEY CONSTRAINT.
Yap, I agree with #peterm.
Cause, if your both Table_A_ID and Table_B_Id are primary keys for both tables, then you don't even need two tables to store the value. Since, your two tables are seems to be on 'one-to-one' relationship. It's one of the database integrity issues.
I think you didn't do proper normalisation for this database.
Just suggesting a good idea!
I found this example which demonstrates how to setup a foreign key constraint.
Create employee table
CREATE TABLE employee (
id smallint(5) unsigned NOT NULL,
firstname varchar(30),
lastname varchar(30),
birthdate date,
PRIMARY KEY (id),
KEY idx_lastname (lastname)
) ENGINE=InnoDB;
Create borrowed table
CREATE TABLE borrowed (
ref int(10) unsigned NOT NULL auto_increment,
employeeid smallint(5) unsigned NOT NULL,
book varchar(50),
PRIMARY KEY (ref)
) ENGINE=InnoDB;
Add a constraint to borrowed table
ALTER TABLE borrowed
ADD CONSTRAINT FK_borrowed
FOREIGN KEY (employeeid) REFERENCES employee(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
NOTE: This tells MySQL that we want to alter the borrowed table by adding a constraint called ‘FK_borrowed’. The employeeid column will reference the id column in the employee table – in other words, an employee must exist before they can borrow a book.
The final two lines are perhaps the most interesting. They state that if an employee ID is updated or an employee is deleted, the changes should be applied to the borrowed table.
NOTE: See the above URL for more details, this is just an excerpt from that article!
Create a foreign key constraint on the column 'Value' on table B that references the 'Table_A_ID' column.
Doing this will only allow values that exist in table A to be added into the 'Value' field of table B.
To accomplish this you first need to make Table_A_ID column the primary key for table A, or it at least has to have some sort of unique constraint applied to it to be a foreign key candidate.
BEGIN TRANSACTION -- REMOVE TRANSACTION AND ROLLBACK AFTER DONE TESTING
--PUT A PRIMARY KEY ON TABLE A
CREATE TABLE A
( Table_A_ID int CONSTRAINT PK_A_Table_A_ID PRIMARY KEY)
--ON VALUE ADD A FOREIGN KEY CONSTRAINT THAT REFERENCEs TABLE A
CREATE TABLE B
( Table_B_ID int,
[Value] int CONSTRAINT FK_B_Value_A REFERENCES A(Table_A_ID)
)
-- TEST VALID INSERT
INSERT A (Table_A_ID) VALUES (1)
INSERT B (Table_B_ID, [Value]) VALUES (1,1)
--NOT ALLOW TO INSERT A VALUE THAT DOES NOT EXIST IN A
--THIS WILL THROW A FOREIGN KEY CONSTRAINT ERROR
INSERT B (Table_B_ID, [Value]) VALUES (1,2) -- 2 DNE in table A
ROLLBACK
Note: there is no magic to 'FK_B_Value_A' or 'PK_A_Table_A_ID' it simply a naming convention and be called anything. The syntax on the foreign key and primary key lines work like this:
column-definition CONSTRAINT give-the-constraint-a-name REFERENCES table-name ( table-column )
column-definition CONSTRAINT give-the-constraint-a-name PRIMARY KEY

Referencing a two column primary key with multiple foreign keys

Take the following two tables in Oracle:
Create Table A
( A int, B int, C int,
Constraint pk_ab Primary Key(A, B),
Unique (C)
);
Create Table B
( D int, E int, F int,
Constraint fk_d Foreign Key (D) References A(A),
Constraint fk_e Foreign Key (E) References A(B)
);
Why doesn't this statement work? Or more specifically, why shouldn't it work? The reason I'm trying to create this type of relation is say, in the future, I want to delete B.D, but keep the relation FK_E.
I'm getting the error:
ORA-02270: no matching unique or primary key for this column-list
"Why doesn't this statement work? Or more specifically, why shouldn't
it work? "
You have defined the primary key on A as a compound of two columns (A,B). Any foreign key which references PK_AB must match those columns in number. This is because a foreign key must identify a single row in the referenced table which owns any given row in the child table. The compound primary key means column A.A can contain duplicate values and so can column A.B; only the permutations of (A,B) are unique. Consequently the referencing foreign key needs two columns.
Create Table B
( D int, E int, F int,
Constraint fk_de Foreign Key (D,E) References A(A,B)
);
"Since there are multiple PK's that table B references"
Wrong. B references a single primary key, which happens to comprise more than one column,
" say, in the future, I want to delete B.D, but keep the relation
fk_e. "
That doesn't make sense. Think of it this way: D is not a property of B, it is an attribute B inherits through its dependence on table A.
One way to avoid this situation is to use a surrogate (or synthetic) key. Compound keys are often business keys, hence their columns are meaningful in a business context. One feature of meaningful column values is that they can change, and cascading such changes to foreign keys can be messy.
Implementing a surrogate key would look like this:
Create Table A
( id int not null, A int, B int, C int,
Constraint pk_a Primary Key(ID),
constraint uk_ab Unique (A,B)
);
Create Table B
( a_id int, F int,
Constraint fk_n_a Foreign Key (A_ID) References A(ID)
);
Of course, you could kind of do this using the schema you posted, as you already have a single column constraint on A(C). However, I think it is bad practice to reference unique constraints rather than primary keys, even though it's allowed. I think this partly because unique constraints often enforce a business key, hence meaning, hence the potential for change, but mainly because referencing primary keys just is the industry standard.
Try create two separate indexes for column's A and B before creating table B
CREATE INDEX a_idx ON A (A);
CREATE INDEX b_idx ON A (B);
But probably you need a compound FK on table B
Create Table B
( D int, E int, F int,
Constraint fk_d Foreign Key (D,E) References A(A,B)
);
A foreign key always references a PK of another table. Neither A nor B
alone are PK's.. You have multiple PK's.
Constraint fk_d Foreign Key (D,E) References A(A,B)
Also the database cannot validate a partial null multiple foreign key, so I think the check constraint also needs to be added to the table.
alter table B add constraint check_nullness
check ( ( D is not null and E is not null ) or
( D is null and E is null ) )

Indexing Foreign keys

How do I index a foreign key in Oracle?
CREATE TABLE reftable (id INT NOT NULL PRIMARY KEY)
CREATE TABLE mytable (id INT NOT NULL, ref INT NOT NULL)
ALTER TABLE mytable
ADD CONSTRAINT fk_mytable_ref_reftable
FOREIGN KEY (ref) REFERENCES reftable (id)
CREATE INDEX ix_mytable_ref ON mytable (ref)
The column in another table (the one you are referencing) must be a PRIMARY KEY or have a UNIQUE constraint defined on it, which means it already has an index.
You cannot index the foreign key constraint itself, but you can index the columns the foreign key is defined on.
Regards,
Rob.