How to declare a foreign key with an OR condition using Oracle? - sql

I have a table (A) whose primary key is either a foreign key to table (B) or table (C).
create table A (
akey number,
txt varchar2(10)
);
create table B (
bkey number,
txt varchar2(10)
);
create table C (
ckey number,
txt varchar2(10)
);
What I want is something like:
alter table A add constraint BorCkey foreign key (akey) references B(bkey)` or C(ckey);
Is this possible?

No, that sort of thing is not possible in Oracle.
Your options generally are
Create two different columns (bkey and ckey) in A where bkey references B.bkey and ckey references C.ckey and create a constraint that ensures that only one is non-NULL at any point in time.
Create some sort of "combined B & C" entity that B & C have foreign keys to and make the foreign key in A reference the key of this combination entity.
If you want a constraint that ensures that exactly one of two columns is NULL and one is NOT NULL for any row
create table one_key(
col1 number,
col2 number,
check( nvl2(col1,1,0) + nvl2(col2,1,0) = 1 )
)

A foreign key constraint is to one foreign table.
That means you'd need to use two ALTER TABLE statements in this situation to setup foreign keys to reference the two tables. There's no opportunity in there to specify an OR in the relationship -- the value in A.akey would have to exist in both B.bkey and C.ckey at the same time. For example, if B.bkey has a value of NULL, but C.ckey does not -- then A.akey can never have a value of NULL. Foreign keys are deferrable in Oracle, but the behavior described is what you will encounter if both foreign keys are enabled at the same time -- you won't be able to enable a constraint if all the values don't satisfy the relationship.
You need to review your needs for how to simplify the relationship so it doesn't need two tables to make this work.

Sounds like you have some form of subtype/supertype relationship going on.
A typical example is 'PERSON' which may be either a 'CUSTOMER' or a 'SUPPLIER'.
You might have, in the PERSON table the unique key of PERSON_ID plus an attribute of PERSON_TYPE ('CUST' or 'SUPP'). If you create the primary key on PERSON_ID,PERSON_TYPE you can reference that in the subtype tables (SUPPLIER/CUSTOMER).
Then you add a unique constraint on the person_id to ensure that any value of person_id must be either a customer or supplier but not both, and check constraints on the subtype tables so that only one type is represented in the table.
create table person
(person_id number,
person_type varchar2(4),
name varchar2(10),
constraint person_pk primary key (person_id, person_type),
constraint person_id_uk unique (person_id));
create table supplier
(supplier_id number,
supplier_type varchar2(4),
blah varchar2(10),
constraint supplier_pk primary key (supplier_id, supplier_type),
constraint supp_pers_fk foreign key (supplier_id, supplier_type)
REFERENCES person (person_id, person_type)
)
/
alter table supplier add constraint supp_type_ck check (supplier_type = 'SUPP');
Its not pretty but types/subtypes are more of an object concept than a relational one.

My solution inspired by Justin:
CREATE OR REPLACE TRIGGER abc
BEFORE INSERT OR UPDATE ON a
FOR EACH ROW
DECLARE
v_testB NUMBER:= 0;
v_testC NUMBER:= 0;
BEGIN
SELECT
COUNT(bkey)
INTO
v_testB
FROM
b
WHERE
bkey = :new.aKey;
SELECT
COUNT(ckey)
INTO
v_testC
FROM
c
WHERE
ckey = :new.aKey;
IF ((v_testB + v_testC) <> 1) THEN
RAISE_APPLICATION_ERROR(-20002,'Foreign key to B or C missing.');
END IF;
END;
/
SHOW ERRORS TRIGGER abc

Create a materialized view that unions tables B & C, and point your FK constraint to the view

Related

Foreign key for inherited table in PostgreSQL

I have tables animals and water inherited from targets table. They look like this:
targets
create table targets
(
id integer not null
primary key,
name text
);
animals (water table is the same)
create table animals
(
type_id integer,
flock_id integer,
primary key (id)
)
inherits (targets);
Also I have a actions table which has a foreign key target_id.
actions
create table actions
(
id integer not null
primary key,
target_id integer
references targets(id),
);
I have some rows in animals and water tables. And I am trying to insert new row into actions table with target_id equals to id of some animal.
INSERT INTO actions (id, target_id)
VALUES (1, 4);
But I receive an error:
[23503] ERROR: insert or update on table "actions" violates foreign key constraint "actions_target_id_fkey"
Key (target_id)=(4) is not present in table "targets".
Maybe I don't understand the table's inheritance, but how can I do my task correctly (target_id should be a foreign key for water and animals tables at the same time)?
I hope this might wrork , you have two tables, animals and water, that inherit from a parent table, targets. You want to create a foreign key in a different table that can reference both the animals and water tables using the id columnn of the targets table.
for that, you can create a foreign key that references the id column of the targets table and use a CHECK constraint to ensure that the referenced row is in either the animals or water table
CREATE TABLE some_table (
id serial primary key,
target_id integer references targets(id),
-- other columns
CONSTRAINT target_id_animals_water_check CHECK (
target_id IN (SELECT id FROM animals)
OR target_id IN (SELECT id FROM water)
)
);
some_table is the name of the table that you want to create the foreign key in, and target_id is the name of the column that will store the foreign key. The references clause specifies that the target_id column should reference the id column of the targets table.
The CHECK constraint ensures that the referenced row is in either the animals or water table. The IN operator is used to check if the target_id is in the set of id values in either the animals or water table.
With this setup, you can insert rows into the animals and water tables as well as the some_table table, and the foreign key constraint will ensure that only valid references are allowed and you can insert a row into the some_table table like this
INSERT INTO some_table (target_id) VALUES (1);

Delete/Modify row in one table based on a condition - Oracle DBMS

I have a table structure like
create table EMPLOYE (
CodeEmploye varchar2(100) not null,
NAS varchar2(100),
CONSTRAINT employe_pk primary key (CodeEmploye)
);
create table SALAIRE (
CodeEmploye varchar2(100) not null,
Mois number not null,
CONSTRAINT salaire_pk primary key (CodeEmploye, Mois),
CONSTRAINT salaire_code_employe_fk FOREIGN KEY(CodeEmploye) REFERENCES EMPLOYE(CodeEmploye)
);
I want to add a constraint where I should not be allowed to modify/delete a row in EMPLOYE table if the same employee exist in SALAIRE table.
What is the best way to do that ?
As you have define the foreign key relationship between two tables by "CodeEmployee" column, what you want has been achieved.
A little bit extension is that if you add "ON DELETE CASCADE" following the fk declaration, once you delete any row form employee table, all the related records in the salary table will be deleted as well.
One of the best ways to do is by creating a foreign key constraint on the "CodeEmploye" column during CREATE TABLE or ALTER TABLE statements. In your case, it is already created (salaire_code_employe_fk) as part of your CREATE statement.
A foreign key constraint ensures that an employee row from the parent table (EMPLOYE) cannot be modified/deleted if the same employee exists in the child table (SALAIRE).
In this case, when you create the order_items table, you define a foreign key constraint with the DELETE CASCADE option as follows:
CREATE TABLE order_items
(
order_id NUMBER( 12, 0 ),
-- other columns
-- ...
CONSTRAINT fk_order_items_orders
FOREIGN KEY( order_id )
REFERENCES orders( order_id )
ON DELETE CASCADE
);
By doing this, whenever you delete a row from the orders table, for example:
DELETE
FROM
orders
WHERE
order_id = 1;
All the rows whose order id is 1 in the order_items table are also deleted automatically by the database system.

SQL: composite primary key, constraint on cascade

I have 3 tables: A, B, C.
The table A contains the attributes: a (primary key)
The table B contains the attributes: a, c (both of them compose a composite primary key)
The table C contains the attributes: c (primary key)
How should I set a constraint to remove the elements of B on cascade, when I remove an entry in A? (using Oracle DBMS).
I have tried this:
ALTER TABLE A ADD CONSTRAINT constraint FOREIGN KEY (a) REFERENCES B (a) ON DELETE CASCADE
But next error is thrown:
ORA-02270: no matching unique or primary key for this column-list
Thanks
Edited:
I´ve added two foreign keys to the table B:
ALTER TABLE B ADD CONSTRAINT FOREIGN KEY (a) REFERENCES A (a) ON DELETE CASCADE
ALTER TABLE B ADD CONSTRAINT FOREIGN KEY (c) REFERENCES C (c) ON DELETE CASCADE
Then, I will remove elements in table B, and the entries in A and C are also removed.
To meet this requirement:
Then, I will remove elements in table B, and the entries in A and C are also removed.
You need to make the B table a parent table for tables A and C by a adding foreign key constraint, that references B table to A and C tables.
Note that the number of referencing columns have to match the number of referenced columns:
create table A(
tab_id number primary key
);
create table B(
col1 number,
col2 number,
constraint PK_Key primary key(col1, col2)
);
create table C(
tab_id number primary key
);
alter table A add ( col1 number
, col2 number
, constraint fk_AB foreign key(col1, col2)
references B(col1, col2) on delete cascade);
alter table C add ( col1 number
, col2 number
, constraint fk_CB foreign key(col1, col2)
references B(col1, col2) on delete cascade);
This is a bit of a guess, but I am assuming table B is a child of of both A and C (perhaps B is a bridging (or cross-reference) table between A and C, where A and C share a many to many relationship).
Table A \*---1 Table B 1---\* Table C (* = many, 1 = one)
I also notice that the FK you are introducing in an identifying FK (by virtue of B(a) being a part of B's primary key).
That makes Table A the parent in this relationship, and B the child. In my experience, any FKs need to be added to the child side of the relationship (in this case, table B).
I'm no Oracle expert, but does this not make more sense?...
ALTER TABLE B ADD CONSTRAINT constraint FOREIGN KEY (a) REFERENCES A (a) ON DELETE CASCADE
This should remove all B rows referring to the PK of any A rows you choose to remove. BUT, I'm not an Oracle expert, so take only at face value until someone with Oracle smarts can confirm (or bomb) my explanation.

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