Reputation: 31
I have three tables in an existing database application. I don't have ERDs or other documentation about the table relations. There are no existing primary or foreign keys.
I would like to model the relation and ideally enforce it with constraints.
The order table can have any number of orders for parts. The value of the Orders.part column must exist in either the Parts_inv table or the Parts_noninv table. I would think the original design should have had one parts table with a type column, but this what I have. Is there a way to model/enforce these relationships?
order char(10)
part char(20)
part_num char(50)
part_num char(50)
Upvotes: 1
Views: 122
Reputation: 35910
You can also create an insert and update Trigger
on the ORDERS
table to fulfill the requirement.
As per your comment, PART_NUM
will be unique across two tables. So you can create the trigger as follows:
CREATE OR REPLACE TRIGGER ORDERS_TRG BEFORE
INSERT OR UPDATE ON ORDERS
FOR EACH ROW
DECLARE
LV_COUNT NUMBER;
BEGIN
SELECT
1
INTO LV_COUNT
FROM
DUAL
WHERE
:NEW.PART IN (
SELECT
PART_NUM
FROM
PARTS_INV
UNION ALL
SELECT
PART_NUM
FROM
PARTS_NONINV
);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE;
END ORDERS_TRG;
/
Update the exception-handling part according to your requirement.
Cheers!!
Upvotes: 0
Reputation:
Here's one way to achieve this, using a materialized view.
In my full working example, I create two tables, T1 and T2. Each has an ID column, and another column that is not the same in the two tables (NAME in one, PRICE in the other, not even the same data type). The task is to create a primary key from the UNION ALL of columns ID from both tables.
First, ID must be PK in each table. Then we create a materialized view (MV) from the UNION ALL of the ID columns from the two tables, and declare the column ID in the MV as the primary key of the MV. Then, in the last step, I create a child table T, with a column ID, foreign key pointing to the MV (not to T1 or T2, but the union all of the ID's from both tables).
You may want to test various things that you could do with the three tables (T1, T2 and T) to see how this all works.
Two things to keep in mind: (1) PK and FK constraints on tables are usually checked at INSERT / UPDATE / DELETE time (if the constraints are "immediate"); this will be the case for the PK constraints on ID, separately for T1 and T2. However, the joint PK constraint on the MV is only checked at COMMIT time. The FK constraint on table T is checked "immediate" (after each INSERT, UPDATE or DELETE - or MERGE - statement). (2) Checking the PK constraint on the MV adds marginal overhead to DML statements on T1 and T2.
So, here goes:
create table t1 (id number primary key, name varchar2(100));
create table t2 (id number primary key, price number not null);
create materialized view log on t1 with primary key, rowid including new values;
create materialized view log on t2 with primary key, rowid including new values;
create materialized view t12 (id, rid, source)
refresh fast on commit
as
select id, rowid, 't1' from t1 union all select id, rowid, 't2' from t2
;
alter materialized view t12 add constraint t12_pk primary key (id);
create table t (id number references t12, eff_date date not null);
Upvotes: 1
Reputation: 50037
Given just the tables you have you can't do this. But that's not to say it can't be done. :-)
Ideally you should change ORDER.PART
to CHAR(50)
to match the PARTS_NUM
fields on the other tables - otherwise you won't be able to store all the possible PARTS_NUM on an ORDER. Perhaps there are business reasons for it being as it is, and perhaps someone was just clueless. No matter - try to fix it up if you can. It won't stop you completely, though.
Create another table - let's call it PARTS_ALL. It has two fields - PARTS_NUM which should match the data type of the PARTS_NUM fields on PARTS_INV and PARTS_NONINV, and SOURCE which should be VARCHAR2(1). Make the combination of (PARTS_NUM, SOURCE) the primary key. So it looks like
CREATE TABLE ALL_PARTS
(PARTS_NUM CHAR(50),
SOURCE VARCHAR2(1) NOT NULL,
CONSTRAINT PK_ALL_PARTS
PRIMARY KEY (PARTS_NUM, SOURCE));
You then copy the data from PARTS_INV and PARTS_NONINV into ALL_PARTS, adding the appropriate SOURCE value:
INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE)
SELECT PARTS_NUM, 'I' AS SOURCE FROM PARTS_INV
UNION ALL
SELECT PARTS_NUM, 'I' AS SOURCE FROM PARTS_NONINV
Now you define a couple of triggers on PARTS_INV and PARTS_NONINV to propagate inserts and deletes on those tables to ALL_PARTS:
CREATE TRIGGER PARTS_INV_AID
AFTER INSERT OR DELETE ON PARTS_INV
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE) VALUES (:NEW.PARTS_NUM, 'I');
ELSIF DELETING THEN
DELETE FROM ALL_PARTS WHERE PARTS_NUM = :OLD.PARTS_NUM;
END IF;
END PARTS_INV_AID;
/
CREATE TRIGGER PARTS_NONINV_AID
AFTER INSERT OR DELETE ON PARTS_NONINV
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE) VALUES (:NEW.PARTS_NUM, 'N');
ELSIF DELETING THEN
DELETE FROM ALL_PARTS WHERE PARTS_NUM = :OLD.PARTS_NUM;
END IF;
END PARTS_NONINV_AID;
/
Now your application suite can continue to INSERT and DELETE rows from PARTS_INV and PARTS_NONINV as they've always done, and those changes will be propagated to ALL_PARTS.
NOW (finally!) you can define your foreign key from ORDERS to ALL_PARTS and get the validation you wanted:
ALTER TABLE ORDER
ADD CONSTRAINT ORDER_FK1
FOREIGN KEY (PART) REFERENCES ALL_PARTS (PARTS_NUM);
Is this ideal? No. IDEALLY you'd get rid of PARTS_INV and PARTS_NONINV, and replace them with ALL_PARTS, modifying all your applications and web apps and back-office software to use the new table - so probably not gonna happen - but given the situation you find yourself in it may be about as good as you can do.
Upvotes: 1
Reputation: 16001
Unfortunately not.
You can define two foreign key constraints, but they will both need to be valid for every row in the table.
The second problem is the choice of char
datatype. No char(10)
value in ORDERS will match a char(50)
in either parent table. Probably the person who chose the data type was not familiar with it.
Upvotes: 0