An easy way to explain my problem is this:
I want to create a table that will contain the information about the subjects that will be dictated in a school. The columns must be:
create table program( teacher varchar(20) references teachers(name),
classroom varchar(20) references classrooms(id),
dtime datetime)
In each one of the tables teachers and classrooms exists a column named 'subject', corresponding to the subject that is dictated by a teacher and the subject that is taught in a classroom (imagine there's no more columns in those tables).
Now, I want to create a constraint in the program table that doesn't let me insert a row where the subject dictated in the classroom is different from the subject dictated by the teacher.
You want to use composite foreign keys. This is going to be simplest by putting the subjectId in the programs table. Here is the example for teachers:
create table teachers (
teacherid int identity(1, 1) primary key,
name varchar(255) not null,
subjectid int references subjects(subjectid),
unique (teacherid, subjectid)
);
create table programs (
programid int identity(1, 1) primary key,
teacherid int references teachers(teacherid),
classroomid int references classrooms(classroomid),
dtime datetime
subjectid int references subjects(subjectid),
foreign key (teacherid, subjectid) references teachers(teacherid, subjectid),
foreign key (classroomid, subjectid) references classrooms(classroomid, subjecdtid)
);
If you don't put the subject in the programs table, then you will need to use a user defined function for the constraint. This seems like a simpler solution.
Related
I have this data model generated by EntityFramework in an existing application but i want to create the same tables with the same relationships in my database.
I succeeded creating the relationship (one to zero or one) between Student and StudentAddress with the following query which is correct:
CREATE TABLE Student(
StudentId INT NOT NULL PRIMARY KEY,
FirstName VARCHAR(30),
LastName VARCHAR(30),
City VARCHAR(30),
State1 VARCHAR(30),
StandardId INT NOT NULL FOREIGN KEY REFERENCES Standard(StandardId));
CREATE TABLE StudentAddress(
StudentId INT,
Adress1 VARCHAR(30),
Adress2 VARCHAR(30),
City VARCHAR(30),
State VARCHAR(30),
CONSTRAINT StudentAdress_PK PRIMARY KEY(StudentId),
CONSTRAINT StudentAdress_Student_FK FOREIGN KEY(StudentId)
REFERENCES Student(StudentId));
My question is considering this data model how to create the relationship (zero or one to many) between Standard and Student tables.
Thank you for your help
You do this the same way that you implemented the one to zero or one relationship.
So far you have a foreign key in StudentAddress to Student. There is nothing enforcing one address per student. (You could do so with a unique index on `StudentAddress.StudentId'.)
So do the same thing. Have a StandardId field in the Student table, and you will have a many Student to zero or one Standard relationship.
You need a third table that stores the relationships:
CREATE TABLE StandardStudenRelation (
ID INT IDENTITY(1,1) PRIMARY KEY
, StandardID INT NOT NULL
, StudentID INT NOT NULL
, CONSTRAINT Student_ID_FK FOREIGN KEY(StudentId)
REFERENCES Standard(StudentId)
, CONSTRAINT Standard_ID_FK FOREIGN KEY(StandardID)
REFERENCES Standard(StandardId)
);
I have a composite primary key that I would like to insert into another table.
create table courses_instructors
(
courseID int foreign key references Course(C_ID) not null,
instructorID int foreign key references Instructor(I_ID) not null,
primary key (courseID, instructorID), --coourseID and instructorID combined is the composite PK
courseTerm varchar(50) not null,
courseNumber int not null,
courseLocation varchar(50),
courseTime varchar(50),
courseMaxOccupancy int,
courseSeatAvailable int
)
create table courses_students
(
studentID int foreign key references student(S_ID) not null,
courseID int, -- foreign key -- I want this value to the be value that represents the composite PK from the courses_instructors
primary key(studentID, courseID), -- these 2 fields combined would make the composite PK, but with the courseID value I will be able to identify who is the instructor for a course and the other details from the course_instructor table
courseOutcome varchar(50)
)
All the course come from a course table which only contains the course name and the disciple along with a descrption. The course table has a primary key that identifies each course uniquely.
To refer composit primary key, Courses_Students table should be having both the columns CourseID and InstructorID.
And then
ALTER TABLE Courses_Students
ADD CONSTRAINT FK_Courses_Students
FOREIGN KEY(CourseID, InstructorID) REFERENCES Courses_Instructors(CourseID, InstructorID)
Or the table definitions should look like,
create table courses_instructors
(
courseID int foreign key references Course(C_ID) not null,
instructorID int foreign key references Instructor(I_ID) not null,
primary key (courseID, instructorID), --coourseID and instructorID combined is the composite PK
courseTerm varchar(50) not null,
courseNumber int not null,
courseLocation varchar(50),
courseTime varchar(50),
courseMaxOccupancy int,
courseSeatAvailable int
)
create table courses_students
(
studentID int foreign key references student(S_ID) not null,
courseID int,
instructorId int,
FOREIGN KEY(CourseID, InstructorID) REFERENCES Courses_Instructors(CourseID, InstructorID),
primary key(studentID, courseID, InstructorId),
courseOutcome varchar(50)
)
You can either refer two columns as already mentioned or add a surrogate key such as an identity column or GUID to the primary table and refer with to it - It usually performs better.
The course_instructors table is an intersection table implementing an m-m relationship between, as may be easily guessed, course entities and instructor entities. Almost invariably, I don't add a surrogate key to such a table for the simple reason that such a key would never be used. A typical user has a reference to one entity or the other and wishes to see all the other entities it relates to. Or sometimes the user has references to both entities and wished to get the details of their relationship.
This rule is not without exceptions, however, and your use case is just such an example. The table not just expresses a relationship between two entities but becomes an entity unto itself: a class offering. A student will select a class from a published schedule for the class and day/time desired. This will be identified by a class code number of some sort.
This code is what will be used to register for the desired class. In cases such as these, it makes sense to create a surrogate key for the intersection table -- which then becomes the class code printed in the catalog. Thus you would use this class code to refer to the relationship that defines the class offering and not use the composite key.
It looks like you already have such a composite key: the course_number field. You don't have it defined as unique but doesn't it uniquely identity the combination of course, instructor, location and time that makes up each class offering?
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.
I am following the technique described in Jeff Smith's "Implementing Table Inheritance in SQL Server" (which seems to be the de facto approach for implementing this kind of structure). The People base table has a 1 : 0..1 relationship with its three subtype tables Students, Teachers, Parents. This is traditionally done by defining the primary key of the subtype tables as a foreign key to the base table.
To enforce exclusivity among the subtypes (preventing the same person from being simultaneously a student and a teacher), the author recommends adding PersonTypeID as a persisted computed column to each of the subtype tables, and including it in the foreign key constraint with the base table.
CREATE TABLE PersonType
(
PersonTypeID INT PRIMARY KEY,
Description VARCHAR(10)
);
INSERT INTO PersonType
VALUES (1, 'Student'),
(2, 'Teacher'),
(3, 'Parent');
CREATE TABLE People
(
PersonID INT PRIMARY KEY,
PersonTypeID INT REFERENCES PersonType (PersonTypeID),
Name VARCHAR(10),
UNIQUE (PersonID, PersonTypeID)
)
CREATE TABLE Students
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 1 PERSISTED, -- student
EnrollmentDate DATETIME,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
CREATE TABLE Teachers
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 2 PERSISTED, -- teacher
HireDate DATETIME,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
CREATE TABLE Parents
(
PersonID INT PRIMARY KEY,
PersonTypeID AS 3 PERSISTED, -- parents
DifficultyScore INT,
FOREIGN KEY (PersonID, PersonTypeID) REFERENCES People (PersonID, PersonTypeID)
)
However, this approach suffers from a number of issues:
It wastes an additional column of space on each of the subtype tables.
It requires an additional unique constraint on the base table. This wastes more space (since it will be implemented as a unique index) and slows down updates to the base table.
The foreign key constraint involves a check on two columns (instead of one), slowing down updates to the subtype tables.
My hypothesis is that it would be better to enforce uniqueness using check constraints through a scalar function instead. This would eliminate the wasted storage for the extra column and unique index, speed up updates to the base table, and hopefully achieve the same performance for updates to the subtype tables as the composite foreign key would.
CREATE TABLE People
(
PersonID INT PRIMARY KEY,
PersonTypeID INT REFERENCES PersonType (PersonTypeID),
Name VARCHAR(10)
)
CREATE FUNCTION GetPersonTypeID (#PersonID INT)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT PersonTypeID
FROM People
WHERE PersonID = #PersonID
)
END;
CREATE TABLE Students
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 1),
EnrollmentDate DATETIME
)
CREATE TABLE Teachers
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 2),
HireDate DATETIME
)
CREATE TABLE Parents
(
PersonID INT PRIMARY KEY REFERENCES People (PersonID)
CHECK (dbo.GetPersonTypeID(PersonID) = 3),
DifficultyScore INT
)
Is there any reason why this approach should not be used?
The additional storage is actually minimal, if your person type column is a tinyint (so up to 255 types of people) you are still only using a single byte extra per person. So this should not be a massive factor in the decision, the main problem is that scalar udfs perform significantly worse than foreign key constraints. This has been tested and the results shown in in the article Scalar UDFs wrapped in CHECK constraints are very slow and may fail for multirow updates .
The testing is also included in this SO answer
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