how do I enforce referential integrity between a many to many table and its parent tables - sql-server-express

I have 4 tables.
Table 1 stores data for Airlines
Table 2 stores data for Destinations
Table 3 stores unique Routes(each route is a unique combination of airline and destination).
Table 4 records the prices of all flights on the routes in table 3. This is a many to many table as there are many flights by each airline to many destinations.
Table 1 = Airlines
Airline_ICAO_Code varchar(3) Not NULL Primary key,
Airline varchar(22) NULL
Table 2 = Destinations
Airport_ICAO_Code varchar(4) Not NULL Primary key,
Destination varchar(30) NULL
Table 3 = Airlines2Destinations
ID int IDENTITY(1,1) NOT NULL,
Airport_ICAO_Code varchar(4) Not NULL Foreign Key References Destinations(Airport_ICAO_Code),
Destination varchar(30) NULL,
Airline_ICAO_Code varchar(3) Not NULL Foreign Key References Airlines(Airline_ICAO_Code),
Airline varchar(22) NULL
Table 4 = Airlines2DestinationsPrices
ID int IDENTITY(1,1) NOT NULL,
Airport_ICAO_Code varchar(4) Not NULL Foreign Key References Destinations(Airport_ICAO_Code),
Destination varchar(30) NULL,
Airline varchar(22) NULL,
Airline_ICAO_Code varchar(3) Not NULL Foreign Key References Airlines(Airline_ICAO_Code),
Departure smalldatetime,
Price smallmoney
My problem is how do I enforce referential integrity between tables 3 and 4. This is necessary as the routes entered in table 4 must be present in table 3. I need advice as to what way to set up the Primary keys on Table 3 and Table 4 that will enable me to do this.
I am considering using a composite key on table 3 like this:
Primary Key(Airport_ICAO_Code,Airline_ICAO_Code)
or concatenating the Airport_ICAO_Code and Airport_ICAO_Code columns into a new column and dropping the ID columns.
If I use the Identity column as the primary key for both tables there is no guarantee that the ID for a route in Table 4 will match the ID for a route in Table 3.
Because of all these options I'm unsure of the best way forward. If someone could take the time to help out on this it would really be appreciated.
Thanks for any help offered
Edit:
After receiving advice from Marc in the post below I set up Table 4 as follows. However there is no primary key on this table. Would the ID column be better for the Primary key or could I use a Composite Key like this Primary Key (Airlines2DestinationsID,Departure).
Table 4 = Airlines2DestinationsPrices
ID int IDENTITY(1,1) NOT NULL,
Airlines2DestinationsID INT NOT NULL
FOREIGN KEY REFERENCES dbo.Airlines2Destiations(ID),
Departure smalldatetime,
Price smallmoney

Basically, if any one entry in table 4 always belongs to a single entry in table 3, I would just store the Airlines2Destiations.ID as a foreign key into table 4.
That way, each entry of table 4 is always clearly and uniquely connected to a single entry in table 3. Also: drop all the redudancy from table 4 - you only need the reference to table 3 - you don't need to repeat airline or airport codes and names.
Table 3 = Airlines2Destinations
ID int IDENTITY(1,1) NOT NULL,
Airport_ICAO_Code CHAR(4) NOT NULL
Foreign Key References Destinations(Airport_ICAO_Code),
Destination VARCHAR(30) NULL,
Airline_ICAO_Code CHAR(3) NOT NULL
Foreign Key References Airlines(Airline_ICAO_Code),
Airline VARCHAR(22) NULL
Table 4 = Airlines2DestinationsPrices
ID int IDENTITY(1,1) NOT NULL,
Airlines2DestinationsID INT NOT NULL
FOREIGN KEY REFERENCES dbo.Airlines2Destiations(ID),
Departure smalldatetime,
Price smallmoney

Related

SQL: check constraint and on delete clauses

I created two tables below:
CREATE TABLE Horse (
ID SMALLINT UNSIGNED AUTO_INCREMENT,
RegisteredName VARCHAR(15),
PRIMARY KEY (ID))
CREATE TABLE Student (
ID SMALLINT UNSIGNED AUTO_INCREMENT,
FirstName VARCHAR(20),
LastName VARCHAR(30),
PRIMARY KEY (ID))
For the third table, I want it to meet the following requirements:
1.HorseID - integer with range 0 to 65 thousand, not NULL, partial primary key, foreign key references Horse(ID)
2.StudentID - integer with range 0 to 65 thousand, foreign key references Student(ID)
3.LessonDateTime - date/time, not NULL, partial primary key
4.If a row is deleted from Horse, the rows with the same horse ID should be deleted from LessonSchedule automatically.
5.If a row is deleted from Student, the same student IDs should be set to NULL in LessonSchedule automatically.
Here is the query:
(For some reason, I was not able to put the codes below in a code block.)
CREATE TABLE LessonSchedule (
HorseID SMALLINT UNSIGNED NOT NULL CHECK(HorseID BETWEEN 0 AND 65000),
StudentID SMALLINT UNSIGNED CHECK(StudentID BETWEEN 0 AND 65000),
LessonDateTime DATETIME NOT NULL,
PRIMARY KEY (HorseID, LessonDateTime),
FOREIGN KEY(HorseID) REFERENCES Horse(ID) ON DELETE CASCADE,
FOREIGN KEY(StudentID) REFERENCES Student(ID) ON DELETE SET NULL)
I got an erroe message: Column 'StudentID' cannot be used in a check
constraint 'lessonschedule_chk_2': needed in a foreign key constraint
'lessonschedule_ibfk_2' referential action.
Here are my questions:
Based on the error message, can anyone tells me what my query went wrong?
Did I do it right putting the two ON DELETE clauses at the end of the two foreign key statements?

Adding an Array of INT column where each value is a primary key from another table

Given two tables like so
CREATE TABLE participants(
id SERIAL PRIMARY KEY,
Name TEXT NOT NULL,
Title TEXT NOT NULL
);
CREATE TABLE meetings (
id SERIAL PRIMARY KEY,
Organizer TEXT NOT NULL,
StartTime DATE NOT NULL,
EndTime DATE NOT NULL,
Participants INT[],
);
I want Participants column of 'meetings' table to contain set of integers which are all primary keys (specific participant) from 'participants' table.
How do I define this field in 'meetings' table ?
The old fashioned way is to create a many-many table, with a couple of commonsense constraints:
CREATE TABLE meetings_participants(
meeting_id int not null,
participant_id int not null,
primary key (meeting_id, participant_id),
foreign key(meeting_id) references meetings(id),
foreign key(participant_id) references participants(id)
)
Now it is easy to add and remove people to meetings be inserting or deleting rows or query meetings that e.g. have 4 or more participants.
A more common approach is to create a junction table for the meeting participants.
CREATE TABLE participants (
participant_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
Name TEXT NOT NULL,
Title TEXT NOT NULL
);
CREATE TABLE meetings (
meeting_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
Organizer TEXT NOT NULL,
StartTime DATE NOT NULL,
EndTime DATE NOT NULL
);
CREATE TABLE meeting_participants(
meeting_id INT NOT NULL,
participant_id INT NOT NULL,
PRIMARY KEY (meeting_id, participant_id),
FOREIGN KEY (meeting_id) REFERENCES meetings(meeting_id),
FOREIGN KEY (participant_id) REFERENCES participants(participant_id)
);
Which is then used to join the 2 tables.
For example:
SELECT m.*, p.*
FROM meeting_participants mp
JOIN meetings m USING(meeting_id)
JOIN participants p USING(participant_id)
WHERE m.Organizer = 'John Doe';

Foreign Key References Two Tables

I'm currently working on a database and I came across a new problem to me. The entities involved are Universe, Competition, Game, Pot. Here are the SQL files to create the tables:
CREATE TABLE Universe (
id int NOT NULL IDENTITY PRIMARY KEY,
history nvarchar (max),
creation_date date
);
CREATE TABLE Pot (
pot_name nvarchar(100),
universe_id int FOREIGN KEY REFERENCES Universe(id),
pot_description nvarchar(100),
media_description nvarchar(100),
is_official_pot bit
PRIMARY KEY (pot_name, universe_id)
);
CREATE TABLE Competition (
universe_id int NOT NULL FOREIGN KEY REFERENCES Universe(id),
compt_name nvarchar(100) NOT NULL,
alias nvarchar(100),
history nvarchar(max),
rules nvarchar (max),
winner_id nvarchar(100) FOREIGN KEY REFERENCES RaulUser(username),
edition int NOT NULL,
is_official_competition bit NOT NULL,
PRIMARY KEY (universe_id, compt_name, edition)
);
CREATE TABLE Game (
id int NOT NULL IDENTITY PRIMARY KEY,
pot_name nvarchar (100) NOT NULL,
universe_id int NOT NULL,
competition_name nvarchar(100) NOT NULL,
competition_edition int NOT NULL,
competition_round int NOT NULL,
home_raul_u_username nvarchar (100) FOREIGN KEY REFERENCES RaulUser(username) NOT NULL,
home_team nvarchar (100) NOT NULL FOREIGN KEY REFERENCES Team(team_name),
home_score int,
away_raul_u_username nvarchar (100) NOT NULL FOREIGN KEY REFERENCES RaulUser(username),
away_team nvarchar (100) NOT NULL FOREIGN KEY REFERENCES Team(team_name),
away_score int,
is_over bit NOT NULL,
played_date date,
FOREIGN KEY (universe_id, competition_name, competition_edition) REFERENCES Competition(universe_id, compt_name, edition),
FOREIGN KEY (universe_id, pot_name) REFERENCES Pot(universe_id, pot_name)
);
The problem starts with this last table (Game), as I can't use universe_id as a Foreign Key for different tables. What's the best approach to solving this? Creating an M:M table Game_Pot?
I only need to record the Pot of each Game because Pots change overtime and I don't want to lose that data.
Sorry for the long post and thank you all in advance :)
The only problem that I see is in the definition of table Game:
FOREIGN KEY (universe_id, pot_name) REFERENCES Pot(universe_id, pot_name)
Ordering of columns matters. The primary key of table Pot is (pot_name, universe_id), so you need to swap the columns in the foreign key, like so:
FOREIGN KEY (pot_name, universe_id) REFERENCES Pot(pot_name, universe_id)
Note that having identity (or the-like) primary key in every table might simplify your design: it would allow you to reduce the number of columns in the children tables, and to use single-column foreign keys. Meanwhile, you can still enforce uniqeness on columns tuples in the parent tables with unique constraints.

SQL Foreign Key across multiple tables

I am modeling inspections. Inspection forms are arbitrary, and essentially are a list of fields.
CREATE TABLE InspectionForms
(
InspectionFormId INT PRIMARY KEY,
InspectionFormName NVARCHAR(64),
... (CreatorId, TimeCreated, etc.)
);
CREATE TABLE InspectionFormFields
(
InspectionFormId INT FOREIGN KEY REFERENCES InspectionForms,
InspectionFormFieldId INT,
FieldName NVARCHAR(64),
PRIMARY KEY(InspectionFormId, InspectionFormFieldId)
);
So, with an InspectionForm named Bathroom, with fields for Toilet, Shower, and Floor, we would have values that look like this:
InspectionForms
---------------
0 Bathroom
InspectionFormFields
--------------------
0 0 Toilet
0 1 Shower
0 2 Floor
Then there are actual completed Inspections:
CREATE TABLE Inspections
(
InspectionId INT PRIMARY KEY,
InspectionFormId INT FOREIGN KEY REFERENCES InspectionForms,
... (InspectorId, TimeOfInspection, etc.)
);
CREATE TABLE InspectionValues
(
InspectionId INT FOREIGN KEY REFERENCES Inspections,
InspectionFormFieldId INT,
Rating TINYINT NOT NULL,
PRIMARY KEY(InspectionId, InspectionFormFieldId)
);
Here are some sample values:
Inspections
-----------
0 0 ...
InspectionValues
----------------
0 0 5 (Inspection 0 scored a 5 in the Toilet)
0 1 3 (Inspection 0 scored a 3 in the Shower)
0 2 4 (Inspection 0 scored a 4 in the Floor)
Here's the kicker: I want InspectionValues to have a FOREIGN KEY referencing InspectionFormFields. But it doesn't have an InspectionFormId column. I can think of two theoretical solutions, but I don't know how to implement either one.
Solution 1: I could simply move the InspectionFormId column from Inspections to InspectionValues, and add my foreign key. That would make our table look like this:
CREATE TABLE InspectionValues
(
InspectionId INT FOREIGN KEY REFERENCES Inspections,
InspectionFormId INT FOREIGN KEY REFERENCES InspectionForms,
InspectionFormFieldId INT,
Rating TINYINT NOT NULL,
PRIMARY KEY(InspectionId, InspectionFormId, InspectionFormFieldId),
FOREIGN KEY(InspectionFormId, InspectionFormFieldId) REFERENCES InspectionFormFields
);
If I do this, I want to somehow enforce that all InspectionValues with a given InspectionId share a common value for InspectionFormId (i.e. I don't want an Inspection to span across multiple InspectionForms). An easy, efficient way to do this would be to make sure that on each update, this query doesn't return any rows:
SELECT InspectionId
FROM InspectionValues a
GROUP BY InspectionId
HAVING MIN(InspectionFormId) < MAX(InspectionFormId);
Solution 2: The InspectionValues table stores a reference to a specific Inspection, which in turn stores a reference to a specific InspectionForm. I could simply create a foreign key pairing InspectionFormFields with InspectionValue-Inspection combinations
If that is not possible, perhaps I could somehow enforce on every update that this query returns no rows:
SELECT *
FROM InspectionValues a
JOIN Inspections b ON a.InspectionId = b.InspectionId
JOIN InspectionForms c ON b.InspectionFormId = c.InspectionFormId
WHERE NOT EXISTS (
SELECT *
FROM InspectionFormFields d
WHERE a.InspectionFormFieldId = d.InspectionFormFieldId AND b.InspectionFormId = d.InspectionFormId
);
I'm using SQL Server 2014, and don't need to support any other version of SQL. What's the right thing to do here?
You should leave InspectionFormId in the Inspections table (rather then move it as you suggest). Since InspectionId is the primary key you cannot have only one inspectionformid per inspectionid.
CREATE TABLE Inspections
(
InspectionId INT PRIMARY KEY,
InspectionFormId INT FOREIGN KEY REFERENCES InspectionForms,
);
Then add the inspectionformid to the inspectionvalues table:
CREATE TABLE InspectionValues
(
InspectionId INT FOREIGN KEY REFERENCES Inspections,
InspectionFormId INT,
InspectionFormFieldId INT,
Rating TINYINT NOT NULL,
PRIMARY KEY(InspectionId, InspectionFormId, InspectionFormFieldId),
FOREIGN KEY(InspectionFormId, InspectionFormFieldId)
REFERENCES InspectionFormFields(InspectionFormId, InspectionFormFieldId)
);
In your solution1 above, InspectionValues you had two foreign keys containing InspectionFormId, but you only need the multi-column one as show above.

SQL Server foreign key constraints issue

I have 2 tables :
dog
dogowner
I am trying to create a foreign key from DogOwner to Dog but not on the Primary Key of the dog table.
I am planning to create my own unique id's for the dog table's dog_id column.
Herewith the schemas :
CREATE TABLE dog(
id INT NOT NULL identity(1,1),
dog_id INT NOT NULL,
dog_name VARCHAR (200) NOT NULL,
create_date DATETIME NOT NULL,
update_date DATETIME DEFAULT getutcdate(),
start_date DATETIME DEFAULT getutcdate(),
end_date DATETIME DEFAULT getDate() - 101,
is_active BIT NOT NULL DEFAULT '0',
PRIMARY KEY (id,dog_id)
);
CREATE TABLE dogowner(
dogowner_id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
dog_id INT NOT NULL,
FOREIGN KEY (dog_id) REFERENCES dog(dog_id)
);
As soon as I create the foreign key constraint on the dogowner table it fails with the following error :
There are no primary or candidate keys in the referenced table 'dog' that match the referencing column list in the foreign key 'FK__dogowner__dog_id__00AA174D'.
> UPDATE :
So eventually I dropped the complicated Schema design and opted for history tables on every table
that I want to version.So the dog table will have a dog_history or dog_log table with a post insert/update done on all the history tables.
This is not the way I wanted to do it but it allows me to have foreign key constraints in my database, soft deletes and logging of data. Thanks all for the input. I am following the KISS principle.
The dog_id field needs to be unique field, the following will work:
create table dog(
id int not null identity(1,1),
dog_id int unique not null,
dog_name varchar(200) not null,
create_date datetime not null ,
update_date datetime default getutcdate(),
start_date datetime default getutcdate(),
end_date datetime default getDate() - 101,
is_active bit not null default '0',
primary key(id,dog_id)
);
create table dogowner(
dogowner_id int not null identity(1,1) primary key,
dog_id int not null,
foreign key(dog_id) references dog(dog_id)
);
From the MSFT documentation:
You can use UNIQUE constraints to make sure that no duplicate values
are entered in specific columns that do not participate in a primary
key. Although both a UNIQUE constraint and a PRIMARY KEY constraint
enforce uniqueness, use a UNIQUE constraint instead of a PRIMARY KEY
constraint when you want to enforce the uniqueness of a column, or
combination of columns, that is not the primary key.
Hope this helps
First create unique key on dog_id field in dog table and create unique constraint on it and then you can refer this unique key as foreign key in Dog_owner table.