How do foreign keys work? - database

I've mostly used MyISAM tables before, which don't support foreign keys. Looking on stack overflow I didn't find a nice, concise explanation of what a foreign key is actually doing. I'm mostly interested in join tables, where you would have a schema like this:
customers
id category_id
products
id category_id
categories
id
customerproducts
customer_id product_id
If I have foreign keys on customerproducts, it will ensure that only valid customers and only valid products get into that table, but what about if I try to add a Product from the phones category to a customer earmarked as one only interested in copiers? Will this cause the foreign key constraints to be violated?

I'm mostly interested in join tables, where you would have a schema like this:
You wouldn't have a schema like that--it doesn't represent the facts you're interested in. Let's sketch out some tables in SQL. (Tested in PostgreSQL) First, customers and products.
-- Customer names aren't unique.
create table customers (
cust_id integer primary key,
cust_name varchar(15) not null
);
insert into customers values (1, 'Foo'), (2, 'Bar');
-- Product names are unique.
create table products (
prod_id integer primary key,
prod_name varchar(15) not null unique
);
insert into products values
(150, 'Product 1'), (151, 'Product 2'), (152, 'Product 3');
There are different categories for products.
create table categories (
cat_name varchar(15) primary key
);
insert into categories values ('Cable'), ('Networking'), ('Phones');
Each product might appear in several categories.
create table product_categories (
prod_id integer not null references products,
cat_name varchar(15) not null references categories,
primary key (prod_id, cat_name)
);
insert into product_categories values
(150, 'Cable'), (150, 'Networking'), (151, 'Networking'), (152, 'Phones');
A customer might be interested in several categories of products.
create table customer_category_interests (
cust_id integer not null references customers,
cat_name varchar(15) not null references categories,
primary key (cust_id, cat_name)
);
-- Nobody's interested in phones
insert into customer_category_interests values
(1, 'Cable'), (1, 'Networking'), (2, 'Networking');
If I have foreign keys on customerproducts, it will ensure that only
valid customers and only valid products get into that table, but what
about if I try to add a Product from the phones category to a customer
earmarked as one only interested in copiers?
Customers aren't interested in every product in their preferred categories. Note the overlapping foreign key constraints.
create table product_interests (
cust_id integer not null,
prod_id integer not null,
cat_name varchar(15) not null,
foreign key (cust_id, cat_name) references customer_category_interests,
foreign key (prod_id, cat_name) references product_categories,
primary key (cust_id, prod_id, cat_name)
);
insert into product_interests values
(1, 150, 'Cable'), (2, 150, 'Networking');
This next insert will fail, because customer 1 isn't interested in phones.
insert into product_interests values
(1, 152, 'Phones');
ERROR: insert or update on table "product_interests" violates foreign key constraint "product_interests_cust_id_fkey"
DETAIL: Key (cust_id, cat_name)=(1, Phones) is not present in table "customer_category_interests".

Related

Mass data loading on table where foreign key reference primary key in same table

I'm teaching myself SQL Server but have a background with "relational" databases for many years and understand keys and their linking relationships.
I have list of pre-defined INSERTS that I found somewhere and am trying and create a "employees" table. Within the table is a primary key on employee_id and with a foreign key on manager_id, references back to employee_id creating a hierarchy.
(
employee_id NUMERIC(3)
PRIMARY KEY,
first_name VARCHAR( 255 ) NOT NULL,
last_name VARCHAR( 255 ) NOT NULL,
email VARCHAR( 255 ) NOT NULL,
phone VARCHAR( 50 ) NOT NULL ,
hire_date DATE NOT NULL ,
manager_id NUMERIC(3) , -- fk
job_title VARCHAR( 255 ) NOT NULL,
CONSTRAINT fk_employees_manager
FOREIGN KEY( manager_id )
REFERENCES employees( employee_id )
ON DELETE NO ACTION
)
Here is snipet of INSERTS:
Insert into EMPLOYEES
(EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (107,'Summer','Payne','summer.payne#example.com','515.123.8181','07-JUN-16',106,'Public Accountant');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (106,'Rose','Stephens','rose.stephens#example.com','515.123.8080','07-JUN-16',2,'Accounting Manager');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (101,'Annabelle','Dunn','annabelle.dunn#example.com','515.123.4444','17-SEP-16',2,'Administration Assistant');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (1,'Tommy','Bailey','tommy.bailey#example.com','515.123.4567','17-JUN-16',null,'President');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (3,'Blake','Cooper','blake.cooper#example.com','515.123.4569','13-JAN-16',1,'Administration Vice President');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (2,'Jude','Rivera','jude.rivera#example.com','515.123.4568','21-SEP-16',1,'Administration Vice President');
Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE,HIRE_DATE,MANAGER_ID,JOB_TITLE) values (11,'Tyler','Ramirez','tyler.ramirez#example.com','515.124.4269','28-SEP-16',9,'Accountant');
Upon trying to INSERT the data, I get an error stating:
The INSERT statement conflicted with the FOREIGN KEY SAME TABLE constraint "fk_employees_manager". The conflict occurred in database "MyDB", table "dbo.employees", column 'employee_id'.
I understand I'm trying to add employee 107 with a manager id of 106 but as being the first INSERT, employee (or a manager) id 106 doesn't exist (yet).
So my quick solution was to create the employees table WITHOUT to foreign key, execute all of the inserts, then finally ALTER the employees table to add the foreign key on manager_id referencing to employee_id.
This worked with no errors.
But I'd like to know if there is a better way to do this. I'm semi aware of the ON CASCADE ON UPDATE, etc features on adding a foreign key, but not sure how/syntax to make this work when creating the table (before data loading).

inserting into multiple related table at once and return id if exists else insert into multiple tables

I am a newbie to SQL/PostgreSQL. I wanted to insert into multiple related tables at once if certain criteria is satisfied else return the existing id of the table. Let me give an example here, following are my exemplary tables.
CREATE TABLE profile (
id serial NOT NULL,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE movies (
movied_id serial NOT NULL,
movie_name VARCHAR NOT NULL,
profile_id INTEGER NOT NULL,
PRIMARY KEY (movie_id),
FOREIGN KEY (profile_id) REFERENCES profile.id
)
CREATE TABLE sports (
sport_id serial NOT NULL,
sport_name VARCHAR NOT NULL,
profile_id INTEGER NOT NULL,
PRIMARY KEY (sport_id),
FOREIGN KEY (profile_id) REFERENCES profile.id
)
CREATE TABLE visited_countries (
country_name VARCHAR NOT NULL,
profile_id INTEGER NOT NULL,
PRIMARY KEY (country_name),
FOREIGN KEY (profile_id) REFERENCES profile.id
)
Here, profile table contains id, first_name, last_name and it is related to movies, sports, visited_countries tables. There exist one to many relation ship between the profile table and all the other tables (Foreign Keys are shown in tables as well). Given this situation:
I want to insert into all these tables at once in the order by getting the unique profile ID first and then use that as FK for all the other tables.
I wanted to check the following before inserting into all of them.
-- first_name, last_nane, and all the movies, sports, visited_countries seen by profile id should be different then only I will insert into all these table at once in the order using the generated profiled ID else just return the profile id.
I have multiple clients writing into the tables at the same time so I wanted to lock and do this transaction in one go.
I read couple of things using CTEs but couldn't get it working correctly. Any suggestion and systematic approach to do this would be helpful.

How to organize table for storing History in DB?

How can I organize additional table/tables for storing Order History?
Right now my DB structure is very simple, see code below:
CREATE TABLE category_table (
id SERIAL PRIMARY KEY NOT NULL,
name CHARACTER(250) NOT NULL
);
CREATE TABLE order_table (
id SERIAL PRIMARY KEY NOT NULL,
total_price DECIMAL(12, 2),
date TIMESTAMP WITH TIME ZONE DEFAULT now(),
product_amount INT
);
CREATE TABLE product_table (
id SERIAL PRIMARY KEY NOT NULL,
name CHARACTER VARYING(250) NOT NULL,
price DECIMAL(12, 2),
category_id INT,
CONSTRAINT category_fk FOREIGN KEY (category_id) REFERENCES category_table (id)
);
CREATE TABLE product_order_table (
product_id INT,
order_id INT,
CONSTRAINT product_fk FOREIGN KEY (product_id) REFERENCES product_table (id),
CONSTRAINT order_fk FOREIGN KEY (order_id) REFERENCES order_table (id)
);
I need to organize table/tables for Order History regarding to these two restrictions:
1. We can delete records form table `order_table`
2. We can change records in `order_table`
I need advice in a DB design.
It is safe to keep tracking the history with another table "order_table_history".
Create trigger function for this table "order_table" and on any operation like insert/update/delete the trigger insert one record in the history table "order_table_history".
Also you can add more columns in the history table, like "user_id/user_name" or "operation_status=delete/update".
CREATE TABLE order_table_history (
id SERIAL PRIMARY KEY NOT NULL,
order_id INT,
total_price DECIMAL(12, 2),
date TIMESTAMP WITH TIME ZONE DEFAULT now(),
product_amount INT
);

Can a foreign key of a table be a part of the composite primary key of the same table?

Suppose I have two tables: A (with columns: a,b,c,d) and B (with columns: x,y,z). Now, (a,b) together make the primary key for table A and x is the primary key of table B. Is it possible to make b a foreign key of table A that refers x from table B?
Please reply ASAP!
Thanks in advance! :-)
Yes, there is no issue with that. A classic example (using MySQL for demonstration purposes) is a database table holding a number of companies and another holding employees which can work for any one of those companies:
create table companies (
id int primary key,
name varchar(20));
create table employees (
id int,
c_id varchar(20) references companies(id),
name varchar(20),
primary key (id, c_id));
insert into companies (id, name) values (1, 'ABC');
insert into companies (id, name) values (2, 'DEF');
insert into companies (id, name) values (3, 'HIJ');
insert into employees (id, c_id, name) values (101, 1, "Allan");
insert into employees (id, c_id, name) values (102, 1, "Bobby");
insert into employees (id, c_id, name) values (101, 2, "Carol");
insert into employees (id, c_id, name) values (101, 3, "David");
Note that the primary key for employees is a composite key made up of the employee ID and company ID. Note also that the company ID is a foreign key constraint on the primary key of companies, the exact situation (functionally) that you asked about.
The query showing who works for what company shows this in action:
select c.id, c.name, e.id, e.name
from companies c, employees e
where c.id = e.c_id
order by c.id, e.id
c.id c.name e.id e.name
---- ------ ---- ------
1 ABC 101 Allan
1 ABC 102 Bobby
2 DEF 101 Carol
3 HIJ 101 David
Can a column in a composite primary key also be a foreign key referencing a primary key of another table? Of course it can. The important question is, when is this a good idea?
The most common scenario is probably the intersection or junction table. Customers can have more than one Address (Shipping, Billing, etc) and Addresses can have more than one Customer using them. So the table CUSTOMER_ADDRESSES has a primary key which references both CUSTOMER and ADDRESS primary key (and for bonus points the ADDRESS_TYPE reference data table too).
My examples use Oracle 12c syntax:
create table customer_address
( customer_id number(38,0) not null
, address_id number(38,0) not null
, address_type_code varchar2(3) not null
, constraint customer_address_pk primary key
(customer_id, address_id, address_type_code)
, constraint customer_address_customer_fk foreign key
(customer_id) references customer(customer_id)
, constraint customer_address_address_fk foreign key
(address_id) references address(address_id)
, constraint customer_address_type_fk foreign key
(address_type_code) references address_type(address_type_code)
);
The second scenario occurs when the primary key of the child table is comprises the parent key and an identifier (usually a number) which is only unique within the parent key. For instance, an Order has an Order Header and some Order Lines. The Order is identified by the Order Header ID and its lines are identified by a monotonically incrementing number. The ORDER_LINE table may look like this:
create table order_line
( order_header_id number(38,0) not null
, order_line_no number(38,0) not null
, product_id number(38,0) not null
, qty number(38,0) not null
, constraint order_line_pk primary key
(order_header_id, order_line_no)
, constraint order_line_header_fk foreign key
(order_header_id) references order_header(order_header_id)
, constraint order_line_product_fk foreign key
(product_id) references product(product_id)
);
Note that we could model ORDER_LINE as another intersection table, with a primary key of (order_header_id, product_id) and relegate order_line_no to the status of ordinary attribute: it depends on the business rules we must represent.
This second scenario is rarer than you might think: composite primary keys are pretty rare in real life. For instance, I think the model presented in that other answer is weak. The chances are we will need to use Employee as a foreign key for a number of relationships (e.g. Manager, Assignment, Sales). Using a composite key for foreign keys is clumsy (more typing!). Furthermore, when we drill into these models we often find that one of the key columns is a natural key rather than a primary key, and so might be subject to change. Cascading changes to natural key columns in composite foreign keys is a PITN.
Hence it is common practice to use a surrogate (or synthetic) primary key, say using a sequence or identify column, and enforce the natural key with a unique constraint. The latter step is often forgotten but it is crucial to maintaining referential integrity. Given a situation in which we need to store details of Employees from several Companies, including the Companies' Employee Identifier we might have an EMPLOYEE table like this:
create table employee
( employee_id number(38,0) generated always as number
, company_id number(38,0) not null
, company_employee_id varchar2(128) not null
, name varchar2(128) not null
, constraint employee_pk primary key
(employee_id)
, constraint employee_uk unique
(company_id, company_employee_id)
, constraint employee_company_fk foreign key
(company_id) references company(company_id)
);
One situation where it is common to find composite primary keys cascaded to dependent tables is in data warehouses and other VLDBs. Here the composite key columns form part of a denormalization strategy to support Partitioning schemes and/or efficient access paths.

integrity constraint in Oracle

Suppose that a table named SuplProd has the columns supplier and product and two entries: (Sony Ericcson, Xperia) and (Apple, iPhone).
I would like to create a table named Orders with columns supplier, product and quantity.
However, I'd like the combination (supplier, product) of table Orders to be limited to contain only entries from SuplProd.
For example, the entry (Sony Ericcson, Xperia, 1) would be valid for the table Orders whereas (Apple, Xperia, 1) would not.
How is this possible in Oracle?
You should create a foreign key in orders table:
create table SuplProd (
supplier ...,
product ...,
constraint SuplProd_pk
primary key( supplier, product)
)
create table Orders
...
supplier ...,
product ...,
qty,
constraint SuplProd_pk
primary key( ... ),
constraint orders_to_suplprod_fk
foreign key ( supplier, product)
references SuplPRod (supplier, product)
)

Resources