Prevent multiple foreign key references to same row in primary table - sql

I have a table containing Employees all of whom have an ID; I'm referencing this ID in two other tables (Salesman, Mechanic) via a foreign key. What I want to ensure is that an employee is either a mechanic or a salesman, but never both. In other words, I want to associate this logic with my foreign keys:
How would I integrate logic like that into a table? I'm a SQL beginner, so I apologize if this is a dumb question.
These are my tables:
CREATE TABLE [dbo].[Employee]
(
Number INT NOT NULL,
-- ...
PRIMARY KEY(Number)
);
CREATE TABLE [dbo].[Salesman]
(
ID INT NOT NULL,
-- ...
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES [dbo].[Employee](ID)
);
CREATE TABLE [dbo].[Mechanic]
(
ID INT NOT NULL,
-- ...
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES [dbo].[Employee](ID)
);

This is a one-of relationship and it is tricky to implement in SQL. Here is one method:
CREATE TABLE [dbo].[Employee] (
EmployeeId INT PRIMARY KEY,
EmployeeType VARCHAR(32)
-- ...
CHECK (EmployeeType IN ('Mechanic', 'SalesPerson')),
UNIQUE (EmployeeType, EmployeeId)
);
CREATE TABLE [dbo].[SalesPerson]
(
EmployeeId INT PRIMAY KEY,
-- ...
EmployeeType as (CONVERT(VARCHAR(32), 'Salesperson')) PERSISTED,
FOREIGN KEY (EmployeeType, EmployeeId) REFERENCES [dbo].[Employee](EmployeeType, EmployeeId)
);
CREATE TABLE [dbo].[Mechanic] (
EmployeeId INT PRIMARY KEY,
EmployeeType as (CONVERT(VARCHAR(32), 'Mechanic')) PERSISTED,
-- ...
FOREIGN KEY (EmployeeType, EmployeeId) REFERENCES [dbo].[Employee](EmployeeType, EmployeeId)
);
Here is a SQL Fiddle illustrating the code.

Related

Can I use one same primary key in two different tables?

'''
CREATE TABLE Employee
(
employeeID INT (10) PRIMARY KEY,
Name CHAR (20)
);
'''
'''
CREATE TABLE SALARY
(
employeeID INT (10) PRIMARY KEY,
Salary INT (10)
);
'''
Is it possible to use the same primary key in both tables?
Yes. You can have same column name as primary key in multiple tables.
Column names should be unique within a table. A table can have only one primary key, as it defines the Entity integrity.
If this question is about data modelling parent-child relationship, There are two types. You are read more on this.
Identifying relationship : Child identifies itself by the help of parent. Here, Employee & Salary will share the same primary key. Primary key of parent table (EmployeeId) will be primary key of child table also (Salary).
Non-Identifying relationship: Child is having its own identity. Here, Employee & Salary will have different primary key. Child table will have its own primary key(say SalaryId) and will have primary key of parent as a Foreign key(EmployeeId).
If your question is if you can use the same EMPLOYEEID column as the primary ID on multiple tables, the answer is "YES, YOU CAN"
You can use the same column as primary index on multiple tables, but you cannot have more than one primary index on a table
Yes, you can. You would make salary.employeeid both the table's primary key and a foreign key to the employee table:
CREATE TABLE salary
(
employeeid INT (10) NOT NULL,
salary INT (10) NOT NULL,
CONSTRAINT pk_salary PRIMARY KEY (employeeid,
CONSTRAINT fk_salary_employeeid FOREIGN KEY (employeeid) REFERENCES employee (employeeid)
);
This creates a {1}:{0,1} relationship between the tables and ensures that you cannot store a salary without having stored the employee and that you cannot store more than one salary for one employee.
This is something we rarely do. (We would rather make the salary a column in the employee table.) The only advantage of a separate salary table I see is that you can grant rights on the employee table but revoke them on the salary table, so as to make the salary table invisible to some database users.
You can do this, however, it is bad design.
I would suggest having the EmployeeId as the PK on the employee table and the EmployeeId as a Foreign Key on the Salary table, with the Salary table having it's own PK (most likely SalaryId).
Also the field [Name] I would personally steer clear of too, as "Name" is a reserved word in SQL.
CREATE TABLE dbo.Employee
(
EmployeeId BIGINT IDENTITY(1,1)
,EmployeeName VARCHAR(20) NOT NULL
,CONSTRAINT PK_Emp PRIMARY KEY (EmployeeId)
);
GO
CREATE TABLE dbo.Salary
(
SalaryId BIGINT IDENTITY(1,1)
,EmployeeId BIGINT NOT NULL
,Salary INT NOT NULL
,CONSTRAINT PK_Sal PRIMARY KEY (SalaryId)
,CONSTRAINT FK_EmpSal FOREIGN KEY (EmployeeId)
REFERENCES Employee(EmployeeId)
);
GO
All of that said, I think a little more thought into the db structure you should most likely end up with 3 tables. It is likely that many staff will have the same salary, lets say 5 employees are on 40,000 and 3 are on 50,000, etc.
You will end up storing the same Salary value multiple times.
A better way is to store that value once and have a third table that links an employee with a salary (in this case I have called it [Earnings]).
With this structure the salary of say 40,000 is stored 1 time in the db and you can link an employeeId to it multiple times.
CREATE TABLE dbo.Employee
(
Id BIGINT IDENTITY(1,1)
,EmployeeName VARCHAR(20) NOT NULL
,CONSTRAINT PK_Emp PRIMARY KEY (Id)
);
GO
CREATE TABLE dbo.Salary
(
Id BIGINT IDENTITY(1,1)
,Salary INT NOT NULL
,CONSTRAINT PK_Sal PRIMARY KEY (Id)
);
GO
CREATE TABLE dbo.Earnings
(
Id BIGINT IDENTITY(1,1)
,EmployeeId BIGINT NOT NULL
,SalaryId BIGINT NOT NULL
,CONSTRAINT PK_Ear PRIMARY KEY (Id)
,CONSTRAINT FK_EmpEar FOREIGN KEY (EmployeeId)
REFERENCES Employee(Id)
,CONSTRAINT FK_SalEar FOREIGN KEY (SalaryId)
REFERENCES Salary(Id)
);
GO
Technically, it is possible.
However, it is advisable to use Foreign key. This will:
-Avoid redundancy
-Help in maintaining db structure
-Improve readability
For this example, use:
CREATE TABLE Employee ( EmployeeID INT PRIMARY KEY, Name CHAR (20), Primary Key (EmployeeId) );
CREATE TABLE SALARY ( SalaryId INT , EmployeeID INT , Salary INT (10), Primary Key (SalaryId), Foreign Key (EmployeeId) REFERENCES Employee );
*An even better approach would be to add a constraint instead of just mentioning
[key names]
Like:
CREATE TABLE Employee(
EmployeeId INT,
Name CHAR(20)
)
ALTER TABLE Employee ADD CONSTRAINT PK_EmployeeId PRIMARY KEY
Can do by differents ways
I have a "Main" table named "Peoples" with "peopleId" as PK Identity
"Child" tables
"PeopleDocs" with "peopleId" PK
"PeopleImages" with "peopleId" PK
"PeopleKeys" with "peopleId" PK.
So, when i create one
record on "Peoples" i populate the "childs" with empty records to work
later.
This logic is about a federal law on my country and some people
informations has a life cicle and must be deleted if the life cicle
ends or the owner of the data demands. So we split the people
sensitive data on child tables to not lose all bussiness records,
refferences and log about that people.
Col1 as Primary key on both tables
SELECT
P.COL1,
P.COL2,
C.COL2,
C.COL3
FROM
TBL1 P, TBL2 C
WHERE
C.COL1 = P.COL1
Or using joins
SELECT
P.COL1,
P.COL2,
C.COL2,
C.COL3
FROM
TBL1 AS P
INNER JOIN
TBL2 AS C
ON
C.COL1 = P.COL1

column "parent_id" referenced in foreign key constraint does not exist when creating SQL table

I am new in SQL and trying to understand Foreign key syntax. I know this was asked in multiple questions but each question I found did not seem to teach me what I am doing wrong here.
This is my SQL code:
CREATE TABLE Customer
(
id int primary key,
name varchar(30),
age int,
gender bool
);
CREATE TABLE Minor
(
FOREIGN KEY (parent_id) REFERENCES Customer(id)
);
CREATE TABLE Adult
(
FOREIGN KEY (parent_id) REFERENCES Customer(id)
);
CREATE TABLE Shop
(
id int primary key
);
CREATE TABLE Drink
(
name varchar(30) primary key
);
CREATE TABLE AlcoholicDrink
(
FOREIGN KEY (name) REFERENCES Drink(name)
);
CREATE TABLE NonAlcoholicDrink
(
FOREIGN KEY (name) REFERENCES Drink(name)
);
And this is the error I am getting:
ERROR: column "parent_id" referenced in foreign key constraint does not exist
SQL state: 42703
You need to add fields in your tables to make the reference. Something like this :
CREATE TABLE Customer
(
id int primary key,
name varchar(30),
age int,
gender bool
);
CREATE TABLE Minor
(
minor_id serial primary key,
parent_id int,
other_fields text etc.
FOREIGN KEY (parent_id) REFERENCES Customer(id)
);
This is simply the reason why it's not working.
Like this
CREATE TABLE Customer
(
id int primary key,
name varchar(30),
age int,
gender bool
);
CREATE TABLE Minor
(
parent_id int ,
FOREIGN KEY (parent_id) REFERENCES Customer(id)
);
To add an answer without just a code snippet:
You've got the right idea with FOREIGN KEY (parent_id) REFERENCES Customer(id). This bit adds a constraint or a "rule" to your table. This constraint ensures that parent_id holds a real id in your Customer table.
The error message told us that column "parent_id" referenced in foreign key constraint does not exist. The confusion comes, understandably, from mistaking the constraint declaration for a column declaration.
As others have pointed out, we need to both 10 declare the foreign key constraint and 2) declare the column.
CREATE TABLE Customer
(
id int primary key,
name varchar(30),
age int,
gender bool
);
CREATE TABLE Minor
(
parent_id int, -- Declare the column
FOREIGN KEY (parent_id) REFERENCES Customer(id) -- Declare the constraint
);

foreign key to a table having more than one primary key

so this is my double primary key table
create table order_mattress
(
order_number int,
mattress_id int ,
primary key (order_number,mattress_id)
);
this is my second table
create table mattress
(
mattress_id int IDENTITY(1,1) PRIMARY KEY,
mattress_name varchar(25)
);
i want to mattress_id in the table mattress to be a foreign key to mattress_id in the table order_mattress how is that possible, without having any problem because of the double primary key in the firsttable
I'd change it so that you have 3 tables as opposed to 2:
Simple order table to hold unique order number:
create table orders
(
order_number int IDENTITY(1,1) PRIMARY KEY,
);
Mattress table that hold all unique mattresses:
create table mattress
(
mattress_id int IDENTITY(1,1) PRIMARY KEY,
mattress_name varchar(25)
);
The order mattress table, then links mattresses to orders with foreign keys.
create table order_mattress
(
order_number int FOREIGN KEY REFERENCES orders(order_number),
mattress_id int FOREIGN KEY REFERENCES mattress(mattress_id)
);
You have the direction wrong - if the mattress table describes mattresses, the mattress_order table should have a foreign key to it:
ALTER TABLE order_mattress
ADD FOREIGN KEY (mattress_id)
REFERENCES mattress(mattress_id)
The other direction is indeed impossible as mattress_id is not unique in order_mattress.

Foreign key on a Foreign key - SQL Server

I'm creating a database for a project in which I need to declare a foreign key on another foreign key for the sake of a checking constraint.
I have a Person table and a Groups table, both of these contain a DepartmentID. Whenever I insert a new task in the Task table, I want to check that the Groups.DepartmentID matches the Person.DepartmentID.
The idea is that a task is linked to a person and has 2 types, a grouptype which defines if its database work, financial work etc and a tasktype which defines if its maintenance, training etc. When a person tries to add a task with a groupType that is not for his/her department it should fail.
I tried adding these attributes to the Task table as a foreign key, however declaring a foreign key on a non-unique or non-primary key isn't accepted in Microsoft SQL Server (the DepartmentID in the Person and Group tables cannot be unique!).
Anyone knows how to fix this?
CREATE TABLE Department
(
ID int PRIMARY KEY IDENTITY,
Name varchar(50),
UNIQUE ("Name")
)
CREATE TABLE Groups
(
ID int IDENTITY,
GroupType varchar(50) PRIMARY KEY,
Description varchar(255) DEFAULT ('-'),
DepartmentID int
FOREIGN KEY (DepartmentID) REFERENCES Department(ID),
)
CREATE TABLE Person
(
ID int PRIMARY KEY IDENTITY,
Name varchar(50),
DepartmentID int
FOREIGN KEY (DepartmentID) REFERENCES Department(ID)
)
CREATE TABLE TaskType
(
ID int IDENTITY,
TaskType varchar(50) PRIMARY KEY,
Description varchar(255) DEFAULT ('-'),
)
CREATE TABLE Task
(
ID int IDENTITY,
TimeFrame decimal(4,2),
Yearcount int,
GroupType varchar(50),
TaskType varchar(50),
WeekNr int,
ExceptionDetail varchar(255) DEFAULT ('-'),
PersonID int
)
These are the FK attributes in the task table that are not accepted:
GDID int FOREIGN KEY REFERENCES Groups(DepartmentID),
PDID int FOREIGN KEY REFERENCES Person(DepartmentID),
CHECK (GDID = PDID),
UNIQUE ("TaskType", "GroupType", "WeekNr", "Yearcount"),
FOREIGN KEY (TaskType) REFERENCES TaskType(TaskType),
FOREIGN KEY (PersonID) REFERENCES Person(ID),
FOREIGN KEY (GroupType) REFERENCES Groups(GroupType)
Add wider "super keys" to these tables that include the primary key and additional columns1, then declare the foreign keys using them. Whether you also remove the superfluous smaller foreign keys is a matter of taste:
CREATE TABLE Groups(
ID int IDENTITY,
GroupType varchar(50) PRIMARY KEY,
Description varchar(255) DEFAULT ('-'),
DepartmentID int FOREIGN KEY (DepartmentID) REFERENCES Department(ID),
constraint Group_Dep_XRef UNIQUE (GroupType,DepartmentID)
)
CREATE TABLE Person(
ID int PRIMARY KEY IDENTITY,
Name varchar(50),
DepartmentID int FOREIGN KEY (DepartmentID) REFERENCES Department(ID),
constraint Person_Dept_XRef UNIQUE (ID,DepartmentID)
)
CREATE TABLE Task(
ID int IDENTITY,
TimeFrame decimal(4,2),
Yearcount int,
GroupType varchar(50),
TaskType varchar(50),
WeekNr int,
ExceptionDetail varchar(255) DEFAULT ('-'),
PersonID int,
DepartmentID int,
constraint FK_Group_Dept_XRef FOREIGN KEY (GroupType,DepartmentID)
references Group (GroupType,DepartmentID),
constraint FK_Person_Dept_XRef FOREIGN KEY (PersonID,DepartmentID)
references Person (ID,DepartmentID),
UNIQUE ("TaskType", "GroupType", "WeekNr", "Yearcount"),
FOREIGN KEY (TaskType) REFERENCES TaskType(TaskType),
FOREIGN KEY (PersonID) REFERENCES Person(ID), --Redundant now
FOREIGN KEY (GroupType) REFERENCES Groups(GroupType) --Also redundant
)
(I also consolidated GDID and PDID into DepartmentID - if they're always meant to be equal, why store that twice and then have to have another constraint to assert their equality?)
1If a primary key (or unique key) is sufficient to uniquely identify each row then any wider key which includes the key columns and additional columns must also be sufficient to uniquely identify each row.

How to implement bidirectional referenced tables?

I have a table School and a table Teacher having a one-to-many relationship. However, one of the teachers is the school's principle, and only one teacher can be the school principle. So I thought of saving the teachers id (principle) in the School table as follows:
CREATE TABLE School (
ID INT PRIMARY KEY,
Name VARCHAR(40),
PrincipleID INT FOREIGN KEY REFERENCES Teacher.ID
)
CREATE TABLE Teacher (
ID INT PRIMARY KEY,
Name VARCHAR(40),
SchoolID INT FOREIGN KEY REFERENCES School.ID
)
I know I could loose the foreign key reference in the school table, but that's not an option.
Should I make the reference after the table creation? If yes, how?
Another solution is to create a new table, let's say SchoolsPrinciples with just two fields:
CREATE TABLE SchoolsPrinciples
(
SchoolId int,
TeacherId int,
CONSTRAINT uc_SchoolTeacher UNIQUE (SchoolId, TeacherId)
)
A UNIQUE constraint let you obtain exactly one teacher per each school.
When building the tables, you'll need to add the constraint as a separate alter statement. Note also that when creating foreign keys, you should only specify the table name, not the referenced column (the column is implied by the primary key).
CREATE TABLE School (
ID INT PRIMARY KEY,
Name VARCHAR(40),
PrincipleID INT);
CREATE TABLE Teacher (
ID INT PRIMARY KEY,
Name VARCHAR(40),
SchoolID INT
CONSTRAINT FK_Teacher_School
FOREIGN KEY REFERENCES School);
ALTER TABLE School add
CONSTRAINT FK_School_Teacher
FOREIGN KEY (PrincipleID) REFERENCES Teacher;
When you add data, you'll need to set the PrincipleID field as a separate update:
insert into School (ID, Name)
values (1, 'Blarg Elementary');
insert into Teacher (ID, Name, SchoolID)
values (1, 'John Doe', 1),
(2, 'Bob Smith', 1),
(3, 'Adam Walker', 1);
update School set PrincipleID = 2 where ID = 1;
Put a boolean IsPrincipal on the Teacher table instead. Or add a third relationship table
CREATE TABLE SchoolPrincipals (
INT SchoolID PRIMARY KEY FOREIGN KEY REFERENCES School.ID,
INT TeacherID FOREIGN KEY REFERENCES Teacher.ID
)
Keeps everything tidy without painful delete logic.
You can take a column in Teacher table as
IsPrincipal where only one row will have value as true as referred
by jonnyGold,
This can be checked by triggers.
OR
You can use filtered index if using Sql Server 2008.
Create unique filtered index where SchoolID, IsPrincipal
is NOT NULL and are unique
Boss where this will contain ID of principal hence creating employee manager relationship which in your case is not suitable.
CREATE TABLE EmpManager
(
TeacherID int
SchoolID int
IsPrincipal bit
)
And use filtered index or trigger to handle the scenario.
EDIT:
CREATE TABLE [dbo].[Teacher](
[ID] [int] NOT NULL primary key,
[Name] [varchar](40) NULL,
[SchoolID] [int] NULL,
)
GO
CREATE TABLE [dbo].[School](
[ID] [int] NOT NULL primary key,
[Name] [varchar](40) NULL,
[PrincipleID] [int] NULL,
)
GO
ALTER TABLE [dbo].[Teacher] WITH CHECK ADD CONSTRAINT [FK_Teacher_School] FOREIGN KEY([SchoolID])
REFERENCES [dbo].[School] ([ID])
GO
ALTER TABLE [dbo].[School] WITH CHECK ADD CONSTRAINT [FK_School_Teacher] FOREIGN KEY([PrincipleID])
REFERENCES [dbo].[Teacher] ([ID])
GO
Better design should be the one suggested by ADC