Foreign key restrictions - mysql

I have two tables say, Hotel with Primary Key Hotel_ID and Staff with Primary Key Staff_ID.
The Hotel table also has another column, Manager_ID referencing Staff_ID of Staff.
The Staff table references Hotel_ID using its column H_ID to indicate which hotel the staff is working at.
I have been trying to figure out if it is possible to restrict allowed values for Manager_ID so that only the staff of that same hotel can be made the manager.
I understand that there is a cross-reference here but my understanding is that it shouldn't be a problem. Could someone tell me how this could be incorporated into the create statement for the Hotel table?

You can add a multicolumn foreign key to select not just the id of the staff, but also the assigned hotel_id inside your hotel table. This would result in a weird looking table structure like this:
Hotel
- Id
- Name
- Manager_Id
- Hotel_Id
Staff
- Id
- Hotel_Id
- IsManager
- Firstname
- Lastname
This is specially looking weird as you have the same id in the Hotel table twice. And for that to work correctly, you have to add triggers to verify that the Id and Hotel_Id value are the same, when the Hotel_Id column is used. And since you have some kind of circle references (Staff references the Hotel table via Hotel_Id, Hotel references the Staff table via Manager_Id) you have to run ALTER TABLE statements to add the additional columns Manager_Id and Hotel_Id. The queries might look like this:
CREATE TABLE Hotel
(
Id INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(30)
);
CREATE TABLE Staff
(
Id INT AUTO_INCREMENT PRIMARY KEY,
Hotel_Id INT NOT NULL,
FirstName VARCHAR(30),
LastName VARCHAR(30),
FOREIGN KEY (Hotel_Id) REFERENCES Hotel(Id),
INDEX (Id, Hotel_Id)
);
The second index is used for the foreign key in the Hotel table:
ALTER TABLE Hotel ADD Manager_ID INT NULL;
ALTER TABLE Hotel ADD Hotel_ID INT NULL;
ALTER TABLE Hotel ADD FOREIGN KEY (Manager_ID, Hotel_ID) REFERENCES Staff(Id, Hotel_Id);
Unfortunately, you cannot use a CHECK CONSTRAINT on the value of the Id column. If you could, it would be possible to write something like:
ALTER TABLE Hotel ADD CONSTRAINT CHK_Hotel CHECK (Hotel_ID = Id OR Hotel_Id IS NULL);
This would verify that the Hotel_Id column, which value come from the Staff table, must be the same as the Id column. However, you will get the following error message:
Check constraint 'CHK_Hotel' cannot refer to an auto-increment column.
You have to add a trigger for INSERT and UPDATE queries (see questions like Constant column value in MySQL table) to make that check.
The following example queries shows how the foreign keys are working:
INSERT INTO Hotel (Name) VALUES ('Some Hotel Name');
SELECT * FROM Hotel;
+----+-----------------+------------+----------+
| Id | Name | Manager_ID | Hotel_ID |
+----+-----------------+------------+----------+
| 1 | Some Hotel Name | NULL | NULL |
+----+-----------------+------------+----------+
INSERT INTO Staff (Hotel_Id, FirstName, LastName) VALUES (1, 'John', 'Doe');
SELECT * FROM Staff;
+----+----------+-----------+----------+
| Id | Hotel_Id | FirstName | LastName |
+----+----------+-----------+----------+
| 1 | 1 | John | Doe |
+----+----------+-----------+----------+
UPDATE Hotel SET Manager_Id = 1, Hotel_Id = 1 WHERE Id = 1;
SELECT * FROM Hotel;
+----+-----------------+------------+----------+
| Id | Name | Manager_ID | Hotel_ID |
+----+-----------------+------------+----------+
| 1 | Some Hotel Name | 1 | 1 |
+----+-----------------+------------+----------+
UPDATE Hotel SET Manager_Id = 1, Hotel_Id = 2 WHERE Id = 1;
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`Hotel`, CONSTRAINT `Hotel_ibfk_1` FOREIGN KEY (`Manager_ID`, `Hotel_ID`) REFERENCES `Staff` (`Id`, `Hotel_Id`))

You can create a flag or role column in the staff table to indicate if the person is a manager or has a specific role in the hotel. You won't need the Manager_Id column in the hotel table anymore. The tables might look like this:
Hotel
- Id
- Name
Staff
- Id
- Hotel_Id
- IsManager
- Firstname
- Lastname
When you add a new stuff you can set the IsManager flag to indicate that this person is the manager of the hotel, identified by the id in the Hotel_Id column.

Related

Validate Record Data from Records in another table

I have 2 tables: Employee, and Regions. Each Employee can be assigned to one region, and each region can have multiple employees. However, I only want the data entered into the Employee record for 'Region' to be one that exists in the Region table. How do I do this?
List of Regions: Northeast, Southeast, Central, Northwest, Southwest
Using MySQL, MySQL Workbench, and Amazon RDS
you add a foreign key constraint to the region table
CREATE tABLE Region (region VARCHAR(55) Primary KEY NOT NULL)
INSERT INTO Region VALUES ('Northeast'), ('Southeast'), ('Central'), ('Northwest'), ('Southwest')
CREATE tABLE employee (id bigint Primary KEY auto_increment
, refregion VARCHAR(55) NOT NULL
,
CONSTRAINT FK_RmploxxRegion FOREIGN KEY (refregion)
REFERENCES Region(region))
INSERT INTO employee VALUES (NULL,'Northpole')
Cannot add or update a child row: a foreign key constraint fails (`db_1115468314`.`employee`, CONSTRAINT `FK_RmploxxRegion` FOREIGN KEY (`refregion`) REFERENCES `Region` (`region`))
INSERT INTO employee VALUES (NULL,'Northeast')
✓
SELECT * FROM employee
id | refregion
-: | :--------
2 | Northeast
db<>fiddle here

Construction of mysql table

I have project for school competition in timekeeping and I am stuck to solve tables construction.
Look at this following examples.
This can happen ->
Competition | Contestants
Swiming | John Smith
Driving | John Smith
You can see that same name is there twice, but not in same competition.
This situation is wrong, so how can i prevent following situation ->
Competition | Contestants
Swiming | John Smith
Swiming | John Smith
I want to avoid duplication in the same competition.
If you can't change the table structure of the table, you could add a unique constraint on the combination of the competition and contestant:
ALTER TABLE competitions
ADD CONSTRAINT competitions_unq UNIQUE (competition, contestant);
But this really isn't a great solution. A more idiomatic approach would be to have separate tables for competitions and contestants, and then have an m:n table to join them:
CREATE TABLE competitions (
id INT PRIMARY KEY,
name VARCHAR(100),
-- other columns
);
CREATE TABLE contestants (
id INT PRIMARY KEY,
first_name VARCHAR(100),
last_name VARCHAR(100),
-- other columns
);
CREATE TABLE competition_contestants (
competition_id INT NOT NULL,
contestant_id INT NOT NULL,
PRIMARY KEY (competition_id, contestant_id),
FOREIGN KEY competition_id REFERENCES competition(id),
FOREIGN KEY contestant_id REFERENCES contestant(id)
);

Unexpected result with Constraints - Database SQL

Trying to figure out why this constraint doesn't work as expected and attempting to find solution.
A description of tables and attributes below:
Table Hotel
hotel_id - primary key - INT(5)
Table Room
room_id - primary key - INT(5)
hotel_id - primary key - INT(5)
room_type - VARCHAR(25)
Table RoomType
room_type - primary key - VARCHAR(25)
hotel_id - primary key - INT(5)
type_description - VARCHAR(100)
price - INT(5)
So the constriants:
room.room_type references roomtype.room_type
room.hotel_id references hotel.hotel_id
roomtype.hotel_id references hotel.hotel_id
So the desired result of this would be that for all the hotels will have room types that are unique to them. Then hotels will also have rooms that are linked to a roomtype. So example data for these would be:
Hotel:
hotel_id
1
2
Room:
room_id hotel_id room_type
1 1 Single
2 2 Double
RoomType:
room_type hotel_id type_description price
Single 1 Random 500
Double 2 Random 500
So if I tried adding this row to the Rooms I would expect it not to work due to constraints. As for hotel_id 1 there is no roomtype Double in the roomtypes table. This still works and doesn't get stopped by the constraint.
Room
room_id hotel_id room_type
3 1 Double
So how would you do this?

Can a foreign key refer to the primary key of its own table?

I am creating a MySQL employee database for work and I want it to store the supervisor of the employee.
Suppose I have a table called 'employee' with the fields 'id', 'first_name', 'last_name', and 'supv_id' where 'id' is the primary key and 'supv_id' is a foreign key that refers to and employee ID.
Currently I have 'supv_id' as a foreign key that points to a separate table 'supervisor'. This table simply consists of 'id' and 'empl_id' which points back to the employee table. However, if there is a way to simply make 'supv_id' in 'employee' to point to 'employee.id', this would eliminate the need of my 'supervisor' table altogether. Here is an example:
+----+--------+-----------+---------+
| id | f_name | l_name | supv_id |
+----+--------+-----------+---------+
| 1 | Han | Solo | NULL | //Or 0?
| 2 | Luke | Skywalker | 1 |
+----+--------+-----------+---------+
In short, I want 'supv_id' to point to another employee. Does this make sense? How would I go about doing this?
Thanks!
Edit: fixed table
You can create such a table as following:
CREATE TABLE laya2 (
id INT NOT NULL PRIMARY KEY,
f_name VARCHAR(20),
l_name VARCHAR(20),
supv_id INT,
INDEX supv_id_idx (supv_id),
FOREIGN KEY (supv_id)
REFERENCES laya2(id)
ON DELETE SET NULL -- example for an action
) ENGINE=INNODB;
My example sets the reference option to SET NULL, because I think it's the logical one here. If an employee who supervises others left, then those employees have no supervisor first. Another option would be to have NO ACTION because you could easily identify those employees without a valid supervisor and find a new supervisor for them. ON DELETE CASCADE would be wrong here, because those employees won't leave at the same time ...
You could insert employees with
INSERT INTO laya2 VALUES
(1, 'Han', 'Solo', NULL),
(2, 'Luke', 'Skywalker', 1);
(two successful inserts), but not with
INSERT INTO laya2 VALUES
(3, 'Anakin', 'Skywalker', 0);
This statement will fail because the foreign key constraint fails.
Deleting Han Solo will change the supv_id for Luke Skywalker to NULL, because of the reference option ON DELETE SET NULL
DELETE FROM laya2 WHERE id = 1; -- this will set the supv_id for Luke Skywalker to NULL
Yes, join the table to itself. Here's one of many ways:
SELECT a.l_name AS employee, b.l_name AS supervisor
FROM employee AS a, employee AS b
WHERE a.supv_id = b.id -- link tables
AND a.id = 2 -- get employee
Returns:
employee | supervisor
----------+-----------
Skywalker | Solo
Yes, you can define a foreign key that refers to the primary key of its own table.
create table employee (id int(10),
f_name varchar(10),
l_name varchar(10),
supv_id int(10)) ENGINE=InnoDB;
alter table employee add primary key (id);
alter table employee add foreign key (supv_id) references employee (id);
Employees without supervisor must have NULL in the supv_id column.

Is it possible to create and delete SQL Enum values based on a column name

So basically if I had a table like so:
ID | Email | Password | PermissionGroup
could I define permission group's enum values based on all the names from the GroupName column values in a different table
ID | GroupName | PermissionNodes | Inheritance
ENUM is not the right tool for this task.
You're describing a foreign key constraint:
CREATE TABLE groups (
ID SERIAL,
GroupName VARCHAR(255),
PermissionNodes VARCHAR(255), -- not sure what datatype this should be
Inheritance VARCHAR(255) -- nor this
) ENGINE = InnoDB;
CREATE TABLE users (
ID SERIAL,
Email VARCHAR(255),
Password CHAR(60) BINARY,
PermissionGroup BIGINT UNSIGNED,
FOREIGN KEY (PermissionGroup) REFERENCES groups (ID)
) ENGINE = InnoDB;
Note that the foreign key normally references the primary key of the parent table. If one wants to obtain the GroupName of a user's group, one would simply join the tables:
SELECT users.*, groups.GroupName
FROM users JOIN groups ON groups.ID = users.PermissionGroup