Composite Foreign Key from multiple related tables - sql

Just beginning to learn about SQL and had a question I couldn't figure out.
I have a setup based on the following tables and their primary keys, the columns with the same name between tables are constrained by foreign keys:
Company:
CompanyId
Division:
CompanyId
DivisionId
Resource:
CompanyId
ResourceId
DivisionResource :
CompanyId
DivisionId
ResourceId
DivisionResource is used to create a many to many relation between division and resource and constrain them so that divisions can only be linked to resources of the same company.
Without the DivisionResource, Division and Resource wouldn't need the CompanyId as a primary key to contain unique records.
So my question is this: Is there a way to create a similar constraint as DivisionResource creates without forcing Division and Resource to have an extra column in its primary key?

ResourceCompany and DivisionCompany in the schema below are connecting tables. They will have CompanyId in their primary key but Resource and Division will have primary keys with one column. This is what you looked for.
Resource -> ResourceCompany
DivisionResource -> ResourceCompany
Division -> DivisionCompany
DivisionResource -> DivisionCompany
create table Company (CompanyId int primary key);
create table DivisionCompany (
CompanyId int foreign key references Company(CompanyId),
DivisionId int,
constraint pk_div_company primary key (DivisionId, CompanyId)
);
create table Division (
DivisionId int primary key,
CompanyId int,
constraint fk_div_company foreign key (DivisionId, CompanyId) references DivisionCompany(DivisionId, CompanyId));
create table ResourceCompany (
CompanyId int foreign key references Company(CompanyId),
ResourceId int,
constraint pk_res primary key (ResourceId, CompanyId));
create table Resource(
ResourceId int primary key,
CompanyId int,
constraint fk_res_company foreign key (ResourceId, CompanyId) references ResourceCompany(ResourceId, CompanyId)
);
create table DivisionResource(
CompanyId int,
DivisionId int,
ResourceId int,
constraint pk_DivRes primary key (DivisionId, ResourceId),
constraint fk_DivCompany foreign key (DivisionId, CompanyId) references DivisionCompany(DivisionId, CompanyId),
constraint fk_ResCompany foreign key (ResourceId, CompanyId) references ResourceCompany(ResourceId, CompanyId)
);

Create INSTEAD OF trigger on insert and update of DivisionResource
The trigger will check if Divistion and Resource have the same company. If they don't, it will fail the modification
Alternatively, it would be even better to have a stored procedure modifying DivisionResource. Then the trigger needs to call it.

I'm assuming that you're trying to create the following schema:
CREATE TABLE company (
companyId int PRIMARY KEY)
CREATE TABLE division (
divisionId int PRIMARY KEY,
companyId int
REFERENCES company (companyId) ON DELETE CASCADE ON UPDATE CASCADE)
CREATE TABLE resource (
resourceId int PRIMARY KEY,
companyId int
REFERENCES company (companyId) ON DELETE CASCADE ON UPDATE CASCADE)
CREATE TABLE divisionResource (
divisionId int
REFERENCES division (divisionId) ON DELETE CASCADE ON UPDATE CASCADE,
resourceId int
REFERENCES resource (resourceId) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (divisionId, resourceId))
Which throws:
Introducing FOREIGN KEY constraint on table 'divisionResource' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Changing either divisionId or resourceId to ON DELETE NO ACTION ON UPDATE NO ACTION will essentially break the referential integrity. What I suggested in my comment was to make a surrogate key. However, it would be better if you created a separate table for resource. This will maintain the referential integrity and also normalize the schema:
CREATE TABLE company (
companyId int PRIMARY KEY)
CREATE TABLE division (
divisionId int PRIMARY KEY,
companyId int
REFERENCES company (companyId) ON DELETE CASCADE ON UPDATE CASCADE)
CREATE TABLE resource (
resourceId int PRIMARY KEY)
CREATE TABLE companyResource (
resourceId int
REFERENCES resource (resourceId) ON DELETE CASCADE ON UPDATE CASCADE,
companyId int
REFERENCES company (companyId) ON DELETE CASCADE ON UPDATE CASCADE)
CREATE TABLE divisionResource (
divisionId int
REFERENCES division (divisionId) ON DELETE CASCADE ON UPDATE CASCADE,
resourceId int
REFERENCES resource (resourceId) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (divisionId, resourceId))

Related

PostgreSQL Composite Foreign Key 'columns list must not contain duplicates'

This is my example schema:
CREATE TABLE users (
userid BIGSERIAL PRIMARY KEY,
name varchar(25) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
FOREIGN KEY (userid1, userid2) REFERENCES users (userid) ON DELETE CASCADE
);
If I run this command, I get:
ERROR: number of referencing and referenced columns for foreign key disagree
And If I add ... REFERENCES users (userid, userid) ON DELETE CASCADE
I get:
ERROR: foreign key referenced-columns list must not contain duplicate
I know it works if I type in each line their respective references, but it would be better to not repeat myself.
How can I achieve that with composite foreign keys using the same dependency?
You need two separate foreign key constraints:
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
FOREIGN KEY (userid1) REFERENCES users (userid) ON DELETE CASCADE,
FOREIGN KEY (userid2) REFERENCES users (userid) ON DELETE CASCADE
);
Though I prefer the syntax shown by Gordon, if you want a one liner you can compress it as:
CREATE TABLE follows (
userid1 int NOT NULL REFERENCES users ON DELETE CASCADE,
userid2 int NOT NULL REFERENCES users ON DELETE CASCADE,
PRIMARY KEY (userid1, userid2)
);
The down side of this short hand notation is that the FKs don't have names. It could be tricky to manage them in the future should you need to remove them or modify them. Experience has shown me that it's better to name them, and for that you need to use the full syntax, as in:
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
CONSTRAINT fk1 FOREIGN KEY (userid1)
REFERENCES users (userid) ON DELETE CASCADE,
CONSTRAINT fk2 FOREIGN KEY (userid2)
REFERENCES users (userid) ON DELETE CASCADE
);
...just to get the fk1 and fk2 identifiers.

SQL Server perform a delete on all child records when deleting from a parent

I have 3 tables:
Create TABLE Subjects
(
SubjectID INT PRIMARY KEY NOT NULL IDENTITY(1,1),
SubjectName VARCHAR(20) NOT NULL,
ClassID VARCHAR(10) FOREIGN KEY REFERENCES Classes(ClassID) NOT NULL
);
Create TABLE Topic
(
TopicID INT PRIMARY KEY NOT NULL IDENTITY(1,1),
TopicName VARCHAR(100),
SubjectID INT FOREIGN KEY REFERENCES Subjects(SubjectID)
);
Create Table Worksheet
(
WorksheetName varchar(100) PRIMARY KEY,
TopicID INT Foreign KEY References Topic(TopicID),
Num_Q INT NOT NULL,
W_Type varchar(30)
);
Each one is a one to many relationship. When I try to delete from Subjects I get a foreign key constraint which is fine. What I want to know is how to get around this and perform a query to delete all relating aspects in a cascading style. I looked it up and there's but I am not sure how it works there seems to be multiple queries. Would it be better to create a trigger or is there a basic cascading function to do it all? I'm using visual studio to perform queries but not sure where the options to perform tasks like this are?
You can add the ON DELETE CASCADE right after the foreign key definition:
Create TABLE Subjects (
SubjectID INT PRIMARY KEY NOT NULL IDENTITY(1, 1),
SubjectName VARCHAR(20) NOT NULL,
ClassID VARCHAR(10) NOT NULL
FOREIGN KEY REFERENCES Classes(ClassID) ON DELETE CASCADE
);
You can also define it as a separate constraint, if you like, either within the CREATE TABLE statement or using ALTER TABLE ADD CONSTRAINT.
Here is the DDL for your Topic table with a CASCADE for delete. Its just a matter of defining it in your FK but using a slightly different syntax. This is for MS Sql Server.
CREATE TABLE Topic
(
TopicID INT PRIMARY KEY NOT NULL IDENTITY(1,1),
TopicName VARCHAR(100),
SubjectID INT,
CONSTRAINT FK_Subjects_Topic FOREIGN KEY (SubjectID)
REFERENCES Subjects (SubjectID)
ON DELETE CASCADE
ON UPDATE NO ACTION
)
EDIT - added DELETE CASCADE on Worksheet table based on comment feedback.
Create Table Worksheet
(
WorksheetName varchar(100) PRIMARY KEY,
TopicID INT,
Num_Q INT NOT NULL,
W_Type varchar(30),
CONSTRAINT FK_Topic_Worksheet FOREIGN KEY (TopicID)
REFERENCES Topic (TopicID)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
With this updated definition a delete on table Subjects will also delete child records in table Topics.

Foreign Key Cascade On Delete

Assume that i have the following tables
User,Article,Comment
A User can Comment on an Article or write an Article.
I wanted the behavior that:
When i delete a User it should delete all of his Articles and
Comments
When i delete an Article it should delete all of its Comments
So i thought i should use FOREIGN KEYS and tried to model the above as the following
CREATE TABLE [User](
UserId int PRIMARY KEY IDENTITY(1,1),
Username nvarchar(50) NOT NULL
)
CREATE TABLE [Article](
ArticleId int PRIMARY KEY IDENTITY(1,1),
UserId int NOT NULL,
FOREIGN KEY(UserId) references [User](UserId) ON DELETE CASCADE
)
CREATE TABLE [Comment](
CommentId int PRIMARY KEY IDENTITY(1,1),
UserId int Not NULL,
ArticleId int NOT NULL ,
FOREIGN KEY(UserId) references [User](UserId) ON DELETE CASCADE,
FOREIGN KEY(ArticleId) references [Article](ArticleId) ON DELETE CASCADE
)
But the problem comes with the Comment causing
Introducing FOREIGN KEY constraint 'FK__Comment__Article__32E0915F' on table 'Comment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My question is how would you model this behavior and still use CASCADE ON DELETE AND FOREIGEN KEYS?
You should just be able to remove the Cascade Delete on the User Foreign Key on the Comment.
As when the user is deleted, this will cascade delete the Article, which will in turn Cascade Delete the comments:
CREATE TABLE [Comment](
CommentId int PRIMARY KEY IDENTITY(1,1),
UserId int Not NULL,
ArticleId int NOT NULL ,
FOREIGN KEY(UserId) references [User](UserId),
FOREIGN KEY(ArticleId) references [Article](ArticleId) ON DELETE CASCADE
)

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.

SQL Cycles or Multiple Cascade Paths

I am having a problem with cycles. I am developing inventory system for IT department.
I am having problem with 3 tables.
INVENTORY table that is used to keep history of user actions and SOFTWARE and HARDWARE tables are used for storing inventory.
I would like to keep a single inventoryID that is unique for SOFTWARE and HARDWARE tables and when I use ON CASCADE DELETE I get an error:
Msg 1785, Level 16, State 0, Line 1
Introducing FOREIGN KEY constraint 'fk_inventory_inventoryIDhw' on table 'inventory' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Here are my tables. Could anyone help me out? How can fix the issue. Can't find right solution on the web. Thanks
CREATE TABLE inventory
(
statusID int,
inventoryStatus nvarchar(15),
inventoryID int,
userName nvarchar(15),
dates datetime2,
inventoryAction nvarchar(10),
categoryID int,
CONSTRAINT pk_inventory_statusID PRIMARY KEY(statusID),
CONSTRAINT fk_inventory_inventoryIDsw FOREIGN KEY(inventoryID) REFERENCES software(inventoryID) ON DELETE CASCADE,
CONSTRAINT fk_inventory_inventoryIDhw FOREIGN KEY(inventoryID) REFERENCES hardware(inventoryID) ON DELETE CASCADE,
CONSTRAINT fk_inventory_userName FOREIGN KEY(userName) REFERENCES users(userName) ON DELETE CASCADE
)
GO
CREATE TABLE hardware
(
inventoryID int,
hardwareID int,
partNumber nvarchar(15),
serial nvarchar(50),
price float,
supportID int,
manufacturerID int,
categoryID int,
description nvarchar,
deviceTypeID int,
CONSTRAINT pk_hardware_inventoryID PRIMARY KEY(inventoryID),
CONSTRAINT fk_hardware_categoryID FOREIGN KEY(categoryID) REFERENCES category(categoryID) ON DELETE CASCADE,
CONSTRAINT fk_hardware_supportID FOREIGN KEY(supportID) REFERENCES support(supportID) ON DELETE CASCADE,
CONSTRAINT fk_hardware_deviceTypeID FOREIGN KEY(deviceTypeID) REFERENCES deviceType(deviceTypeID) ON DELETE CASCADE,
CONSTRAINT fk_hardware_manufacturerID FOREIGN KEY(manufacturerID) REFERENCES manufacturer(manufacturerID) ON DELETE CASCADE,
)
GO
CREATE TABLE software
(
inventoryID int,
softwareID int,
version nvarchar(10),
name nvarchar(50),
license nvarchar(50),
price float,
supportID int,
categoryID int,
manufacturerID int,
programTypeID int,
description nvarchar,
CONSTRAINT pk_software_inventoryID PRIMARY KEY(inventoryID),
CONSTRAINT fk_software_categoryID FOREIGN KEY(categoryID) REFERENCES category(categoryID) ON DELETE CASCADE,
CONSTRAINT fk_software_supportID FOREIGN KEY(supportID) REFERENCES support(supportID) ON DELETE CASCADE,
CONSTRAINT fk_software_programTypeID FOREIGN KEY(programTypeID) REFERENCES programType(programTypeID) ON DELETE CASCADE,
CONSTRAINT fk_software_manufacturerID FOREIGN KEY(manufacturerID) REFERENCES manufacturer(manufacturerID) ON DELETE CASCADE
)
GO
As far as I can remember multiple fk constraints on the same column associated with more than one parent table is not workable. You might need another table to maintain this relationship. Please see: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=173589
Having the same column as part of multipe Foriegn Key Constraints associated with multiple parent tables create referential integrity issues.
The best approach would be to normalise the design and introduce an intermediate table to retain the relation as 2 (or more) relations.