Skip to Main Content
  • Questions
  • Primary and foreign key in temporal tables

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Amit.

Asked: February 10, 2018 - 5:03 pm UTC

Last updated: February 19, 2018 - 4:55 pm UTC

Version: Oracle 11g

Viewed 1000+ times

You Asked

I have product and supplier table. Both tables have start and end date columns (temporal validity)
Relation between product and supplier table is managed via a link table product_supplier_lnk table.
I need to know how to manage primary and foreign key in product and product_supplier_lnk table.

Table info:
Product:
Id (oracle sequence , primary key)
LinkId (unique)
Name varchar
Start_date date
End_date date

Supplier:
Id (oracle sequence , primary key)
LinkId (unique)
Name
Start_date date
End_date date

Product_supplier_lnk

Id (oracle sequence , primary key)
.
If i will refer id's of product and supplier table in link table then for any change in product or order table (change in name column). I have to insert new links in link table and set end_date in old links.
This causes performance issues.
If i will use linkid of both table in link table as logical key then it will not let me reset links in link table. I need to reset link if product is mapped to new supplier.
Will it be a good option or it has any drawback refering linkid's and not id's?





and Chris said...

The question is:

Where do you need to manage version specific relations and why?

For things like orders and invoices, often you need to know exactly what the customer ordered to ensure you deliver the correct product. So there you need to store the exact version they ordered.

But it's usually less relevant when finding out who supplies which products. Only who supplies what at a given time. So you only want to store product codes to suppliers. The problem here is you can't then have time-independent FKs back to the product code in the history:

create table product_versions (
  product_id   int primary key,
  product_code int not null,
  product_name varchar2(10) not null,
  start_date   date not null,
  end_date     date,
  unique ( product_code, start_date )
);

create table product_suppliers (
  product_code  int not null,
  supplier_code int not null,
  start_date    date not null,
  end_date      date,
  unique ( product_code, supplier_code, start_date )
);

alter table product_suppliers add constraint product_fk 
  foreign key ( product_code ) references product_versions ( product_code );

ORA-02270: no matching unique or primary key for this column-list


I'm not sure what your link_id attribute represents. If this is a product or supplier code, you need to combine it with start_date to make it unique across the table. If it's another value generated for each row it has no extra value over the sequence id.

In any case, I suspect what you need is a single code that applies to several versions of a product. To get around the problem above, create a table to store this and add the relevant foreign keys:

create table products (
  product_code int primary key
);

alter table product_suppliers add constraint prsu_product_fk 
  foreign key ( product_code ) references products ( product_code );
  
alter table product_versions add constraint prve_product_fk 
  foreign key ( product_code ) references products ( product_code );


If you do want to store the specific version of a product a supplier provides and keep this current, you can do so with an API like:

create or replace procedure update_product (
old_prod_id int, product_name int
) as
new_prod_id int := product_seq.nextval ;
new_start_date date := sysdate;
begin
insert into product_versions (
product_id, product_name, start_date
)
select new_prod_id, nvl(update_product.product_name, pv.produce_name), --etc.
new_start_date
from product_versions pv
where pv.product_id = update_product.old_prod_id;

update product_versions pv
set end_date = new_start_date
where product_id = old_prod_id;

update product_suppliers ps
set ps.product_id = new_prod_id
where ps.product_id = old_prod_id
/* Only active rows */
and ps.start_date < sysdate
and ps.end_date is null;

/* ... further updates to non-version specific tables ... */

end update_product;
/</code>

This should be reasonably efficient. If your performance issue relate to something like this API, please provide more details so we can help you, speficially:

- DDL for the tables and indexes involved (create table + create index)
- The DML statements you're running
- Execution plans for these statements. Read this article more details on how to do this:

https://blogs.oracle.com/sql/how-to-create-an-execution-plan

Is this answer out of date? If it is, please let us know via a Comment

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.