Subtype disjointness - sql

I've looked through the forums, but I can't seem to find exactly what I'm looking for. I have a supertype, employee, and three subtypes that reference employee's primary key, ID. The subtypes of employee must be disjoint. My problem arises in that I don't understand where to place a constraint to make this happen.
CREATE TABLE Employee(
ID INT,
PRIMARY KEY(ID));
CREATE TABLE Manager(
ID INT,
Salary INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
CREATE TABLE Server(
ID INT,
Tips INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
CREATE TABLE Hostess(
ID INT,
hourly_sal INT NOT NULL,
PRIMARY KEY(ID),
FOREIGN KEY(ID) REFERENCES Employee(ID));
I thought to create the constraint via a view of intersecting values, then a constraint limiting the view's entries to null only, as shown below:
CREATE VIEW EMPLOYEE_DISJOINT AS
((SELECT ID FROM Server)INTERSECT (SELECT ID FROM Hostess))
UNION
((SELECT ID FROM Hostess) INTERSECT (SELECT ID FROM Manager))
UNION
((SELECT ID FROM Server) INTERSECT (SELECT ID FROM Manager));
ALTER VIEW EMPLOYEE_DISJOINT
ADD CONSTRAINT disjoint CHECK(ID = NULL);
Seeking to create a constraint on the view requiring all of the primary keys in Employee to be unique to one and only one subtype of employee. Is there a better way to do this? While this method seems like it ought to work, I get the following error:
ADD CONSTRAINT disjoint CHECK(ID = NULL)
*
ERROR at line 2:
ORA-00922: missing or invalid option
Please help or point me in the direction of somewhere I may find it! Thanks a ton!

You could use a materialized view and add the CHECK (ID=NULL) constraint, but a simpler method might be just to have a discriminator column on the Employee table, e.g. employee_type with valid values ('Manager', 'Server', 'Hostess').

Related

SQL Constraint names

If we have query for creating table like this..
create table if not exists food
(
id int not null auto_increment,
user_id int,
name varchar(30),
constraint pk_food primary key(id,name),
foreign key(user_id) references userss(id)
);
What does pk_food mean in this example? I know this is a constraint name, but for what we should be give a name for constraint, if its working without?
create table if not exists food
(
id int not null auto_increment,
user_id int,
name varchar(30),
primary key (id, name),
foreign key (user_id) references userss(id)
);
I mean.. how to use these names and for what we need it?
You gives constraints names for basically two reasons:
You can better understand the error message when the constraint is violated.
You can more easily find the constraint if you want to delete it.

How to address a multiple purpose column in sql?

Scenario
I need to design a equipment movement from many sources and destinations. I have the folowing sample tables:
CREATE TABLE Area{
Id INT,
Name VARCHAR(50),
//some other fields
}
CREATE TABLE Stowage{
Id Int,
Name VARCHAR(50),
//some other fields
}
CREATE TABLE Movement{
OriginId INT,
DestinationId INT,
}
But I need some kind of movement like:
Origin : Area; Destination: Area
Origin : Area; Destination: Stowage
Origin : Stowage; Destination: Area
Origin : Stowage; Destination: Stowage
But I only have two columns and needs more than one foreign key per column.
Posible solution in mind
Create MovementArea, MovementStowage, MovementStowageArea tables and create its propertly foreigns keys.
Don't create foreigns key for the columns OriginId and DestinationId and fill it as needed.
Final Question
Is there another way to address this in sql or which of the provided solutions is most aceptable for this scenario?
Tricky. You have 4 foreign keys, so I would [naturally] create 4 foreign key columns, as in:
create table movement (
origin_area int,
origin_stowage int,
dest_area int,
dest_stowage int,
constraint fk1 foreign key origin_area references area (id),
constraint fk2 foreign key origin_stowage references stowage (id),
constraint fk3 foreign key dest_area references area (id),
constraint fk4 foreign key dest_stowage references stowage (id),
constraint chk_fk1 check (origin_area is null and origin_stowage is not null
or origin_area is not null and origin_stowage is null),
constraint chk_fk2 check (dest_area is null and dest_stowage is not null
or dest_area is not null and dest_stowage is null)
);
Now, as you see:
There are 4 nullable FK columns.
Each FK column has its corresponding FK constraint.
Also, origin_area and origin_stowage are mutually exclusive. Always one of them is null, while the other points to the other table. This is enforced by the constriaint chk_fk1.
The same can be said for dest_area and dest_stowage. Enforced by chk_fk2.
My first thought is something like this:
Create Table MovementEndpoint
(
ID Int
, Name Varchar(50)
, EndpointType Int -- Area, Stowage, etc
, EndpointDetailID Int -- FK to Area, Stowage, etc
)
Now your movements just go between endpoints, and the MovementEndpoint record lets you get to the Area or Stowage record as needed. The query logic will still be a little tricky, but no more so than your initial design requires.

How to insert a composite primary key into another table?

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?

How do I create a table whose rows reference 1 (and only 1) of 2 existing tables?

Here's my situation: I have two tables created with
CREATE DATABASE JsPracticeDb;
/* Create tables corresponding to the problems, solutions to
problems, and ratings of problems or solutions */
CREATE TABLE Problems (
id INT PRIMARY KEY NOT NULL,
prompt_code VARCHAR(3000),
test_func_code VARCHAR(3000),
test_input_code VARCHAR(3000)
);
CREATE TABLE Solutions (
id INT PRIMARY KEY NOT NULL,
problem_id INT,
solver_name VARCHAR(50),
code VARCHAR(3000),
FOREIGN KEY (problem_id) REFERENCES Problems(id) ON DELETE CASCADE,
);
and I was thinking about creating a table for rating Solutions, which I wrote as
CREATE TABLE Ratings (
id INT PRIMARY KEY NOT NULL,
solution_id INT,
stars TINYINT,
FOREIGN KEY (solution_id) REFERENCES Solutions(id) ON DELETE CASCADE
);
but then I realized I might actually want to have Problems rated as well. The "brute force" solution, as I see it, is
CREATE TABLE SolutionRatings (
id INT PRIMARY KEY NOT NULL,
solution_id INT,
stars TINYINT,
FOREIGN KEY (solution_id) REFERENCES Solutions(id) ON DELETE CASCADE
);
CREATE TABLE ProblemRatings (
id INT PRIMARY KEY NOT NULL,
problem_id INT,
stars TINYINT,
FOREIGN KEY (problem_id) REFERENCES Problems(id) ON DELETE CASCADE
);
but my programming intuition says there's a problem with the fact that I used copy-paste to write two sections of code that are almost identical. However, I can't think of any alternative solution that uses an intersection table or something like that also allows me to do a cascade delete. For example, I know I could do
CREATE TABLE RatedTables (
id TINYINT PRIMARY KEY NOT NULL,
table_name VARCHAR(9)
);
INSERT INTO RatedTables (table_name) VALUES ('Problems','Solutions');
CREATE TABLE Ratings (
id INT PRIMARY KEY NOT NULL,
rated_table_id TINYINT NOT NULL,
stars TINYINT,
FOREIGN KEY (rated_table_id) REFERENCES RatedTables(id)
);
but then how would I make it so that if a Solution with corresponding Ratings was deleted then those ratings would be too?????
You basically have two options but this is a good opportunity to go back and review your db structure.
The first option is to do something like this:
CREATE TABLE potential_link1 (
id int primary key,
...
);
CREATE TABLE potential_link2 (
id int primary key,
....
);
CREATE TABLE ratings (
id int primary key,
potential_link1 int references potential_link1(id) on delete cascade,
potential_link2 int references potential_link2(id) on delete cascade,
....
check(potential_link1 is null or potential_link2 is null),
check(potential_link2 is not null or potential_link1 is not null)
);
This works but as you can see it is a bit complex.
The second possibility is that since there are clear cases where a is dependent on the union of b and c then you may think about whether you can refactor your db structure to reflect that so you only need one table to link against.
There is nothing wrong with two tables looking so much alike. They contain different things and you won't want to select all three-star ratings no matter whether on problems or solutions for instance - you would always work with solution ratings or problem ratings.
But to have both ratings in one table is also not wrong and can be a good idea when you want ratings to behave the same, no matter whether on problem or solution (e.g. both shall have 1 to 5 stars, both can have a comment no longer then 200 chars, ...).
This could be done by simply giving the ratings table both a problem_id and a solution_id with foreign keys on the tables and fill always one or the other. With natural keys, the same would feel even more, well, natural:
problem(problem_no, data)
solution(problem_no, solution_no, data)
rating(problem_no, solution_no, data)
with rating.solution_no nullable and foreign keys on both parent tables.

SQL constraint on a foreign key

I am having problems creating a constraint for a foreign key field, so that a foreign key can only be entered if that foreign key links to a row containing a specific attribute.
To give a better idea about what I mean, I have created the following example. The Employee entity table contains a Grade field, which can either be 'S' for a senior level employee, or 'J' for a junior level employee. In the Expenses table, I want to limit any entries into the ApprovedBy field to those EmpNo values that have a Grade field containing 'S' in the Employee table.
CREATE TABLE Employee
(EmpNo INT PRIMARY KEY,
FirstName VARCHAR(15),
LastName VARCHAR(15),
Grade CHAR(1),
CONSTRAINT chk_ValidGrade CHECK (Grade IN ('J','S'))
)
CREATE TABLE Expenses
(ExpenseId INT IDENTITY(1,1) PRIMARY KEY,
Amount FLOAT,
ApprovedBy INT FOREIGN KEY REFERENCES Employee(EmpNo),
)
I instinctively want to use a join, or other relational algebra function to do this. However, my understanding is that you can't use the SELECT function within a CHECK function, so I'm not sure how I would define the constraint.
As far as I know, you cannot use check constraint for this task. You have to use a trigger for insert and update in your Expenses table.