MySQL foreign key constraints through more tables - mysql

I find myself stack to this problem. I've got the following 3 tables, which I can't modify (it'd be so nice):
Person
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| SSN | varchar(50) | NO | PRI | NULL | |
| name | varchar(50) | YES | UNI | NULL | |
| birthday | date | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Employee
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| SSN | varchar(50) | NO | PRI | NULL | |
| department | varchar(50) | YES | | NULL | |
| salary | varchar(50) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Employer
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| name | varchar(50) | NO | PRI | NULL | |
| department | varchar(50) | YES | | NULL | |
| salary | varchar(50) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Contract
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| employer_name | varchar(50) | NO | PRI | NULL | |
| employee_name | varchar(50) | YES | PRI | NULL | |
+----------------+-------------+------+-----+---------+-------+
I know that:
employee(sin) is foreign key to person(sin),
employer(name) is foreign key to person(name),
contract(employer_name) is a foreign key to employer(name)
And I should insert another foreign key for contract(employee_name), though employee table has only the SSN. Is there a way to reference the foreign key to person.name, passing through employee table, something like
CONSTRAINTS FOREIGN KEY contract(employee_name) REFERENCES TO person(name) WHERE person(sin)=employee(sin);
?
Thank you very much for any help!

An SQL FK constraint says its referencing column list subrow values, if all non-NULL, have to appear as subrow values for its referenced column list, which must be declared UNIQUE/PK in a base table.
Your constraint is not an SQL FK constraint.
If we could use a query in the place of the referenced base table name in an SQL FK constraint then the constraint you would want would be:
Contract(person_name) REFERENCES
(SELECT name FROM Person p JOIN Employee e ON p.name = e.name)(name)
(It can be shown that name is UNIQUE in that table.) But we can't.
If MySQL supported CREATE ASSERTION then you could CHECK that every Contract person_name was IN (SELECT name FROM Person p JOIN Employee e ON p.name = e.name). But it doesn't.
So this is an example of a constraint that, if you can't redesign your tables, you would enforce by appropriate triggers when the tables involved change.
Is there a way to reference the foreign key to person.name, passing through employee table, something like
If you want to figure out situations like this then you have to forego the cop-outs of using precise terms like "foreign key" just because you are somehow reminded of the things they refer to but that aren't there, or using the poetic "passing through", or using the vague "something like". You have to actually write out the conditions that you want your tables to satisfy.

Related

Does the foreign key slow down the join query?

I have two databases test & test2. Both have the same tables(employees & salaries) and both have the same records. test2 database uses a foreign key and test database doesn't.
test structure
test.employees
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| emp_id | int(11) | NO | PRI | NULL | |
| name | varchar(30) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
test.salaries
+--------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| salary | int(11) | YES | | NULL | |
| emp_id | int(11) | NO | | NULL | |
+--------+---------+------+-----+---------+----------------+
test2 structure
test2.employees
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| emp_id | int(11) | NO | PRI | NULL | |
| name | varchar(30) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
test2.salaries
+--------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| salary | int(11) | YES | | NULL | |
| emp_id | int(11) | NO | MUL | NULL | |
+--------+---------+------+-----+---------+----------------+
I run the same join query on both databases
select * from employees inner join salaries on employees.emp_id=salaries.emp_id;
This is the output i get from test database which doesn't contain a foreign key
2844047 rows in set (3.25 sec)
This is the output i get from test2 database which contains a foreign key
2844047 rows in set (17.21 sec)
So does the foreign key slow down the join query?
Your empirical evidence suggests that in at least one case it does. So, if we believe your numbers, the answer is clearly "yes" -- and I assume you have ruled out other potential causes such as locks on the table or resource competition (actually the difference is pretty big). I presume that you want to know why.
In most databases, declaring a foreign key is about relational integrity. It would have no effect on the optimization of queries. The join conditions in the query would redundantly cover the same information.
However, MySQL does a bit more when a foreign key is declared. A foreign key declaration automatically creates an index on the columns being used. This is not standard behavior -- I'm not even sure if any other database does this.
Normally, an index would benefit performance. In this case, the optimizer has more choices on how to approach the query. For whatever reason, it is using a substandard execution plan.
You should be able to look at the explain plans and see a difference. The issue is that the optimizer has chosen the wrong plan. I would say that this is uncommon and should not dissuade you from using proper foreign key declarations in your databases.

Properly creating a relational database

I have a database with three tables.
The table Authentication contains the following:
+----------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------------+------+-----+---------+----------------+
| id | int(6) unsigned | NO | PRI | NULL | auto_increment |
| userid | varchar(30) | NO | | NULL | |
| password | varchar(30) | NO | | NULL | |
| role | varchar(20) | NO | | NULL | |
| email | varchar(50) | YES | | NULL | |
+----------+-----------------+------+-----+---------+----------------+
Login contains the following:
+--------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------+------+-----+---------+----------------+
| id | int(6) unsigned | NO | PRI | NULL | auto_increment |
| TimeLoggedIn | text | NO | | NULL | |
| sessionid | varchar(255) | NO | | NULL | |
+--------------+-----------------+------+-----+---------+----------------+
And Activity:
+----------+-----------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------------+------+-----+---------+-------+
| id | int(6) unsigned | NO | PRI | NULL | |
| Torrents | mediumtext | NO | | NULL | |
+----------+-----------------+------+-----+---------+-------+
There is a relation between the id fields of Authentication to id in the other tables.
I need to add multiple rows in Activity, with several values for Torrents for each id. Unfortunately, when I try adding a new row with duplicated id value with:
INSERT INTO `Activity` (`id`, `Torrents`) VALUES ('1', 'dssfsdffdsffs');
it gives me the error: #1062 - Duplicate entry '1' for key 'PRIMARY'
How do I solve it? How did I create the table wrong?
I've read the following apparently duplicate questions:
#1062 - Duplicate entry for key 'PRIMARY'
But though it says to remove it as my primary key, mysql didnt allow me to create a relationship unless I made it a primary key.
You cannot initiate one-to-many relation by referring primary key to primary key. That'll be a one-to-one relationship.
In both your Login and Activity tables you need to have a foreign key to refer back to Authentication's ID. Example:
CONSTRAINT `FK_Login` FOREIGN KEY (`AuthenticationID`) REFERENCES `Authentication` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE

MySQL: Cannot add foreign key constraint Error

The first table is LocationTime table and the second table is Student table.
I am adding a Foreign Key using 'ALTER'. But I got the error below. Why am I getting the error?
Cannot add foreign key constraint Error
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| CourseN | varchar(3) | NO | PRI | NULL | |
| Quarter | varchar(11) | NO | PRI | NULL | |
| DayTime | varchar(7) | NO | PRI | NULL | |
| RoomN | varchar(3) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| studentName | varchar(15) | NO | PRI | NULL | |
| CourseN | varchar(3) | NO | PRI | NULL | |
| Quarter | varchar(11) | NO | PRI | NULL | |
+-------------+-------------+------+-----+---------+-------+
ALTER TABLE Student
ADD FOREIGN KEY (Quarter) REFERENCES LocationTime (Quarter)
ON UPDATE CASCADE ON DELETE SET NULL;
The problem is the fact that Quarter is a member of a composite key (CouresN, Quarter and DayTime forms the primary key for the LocationTime table). It is not possible to declare a Foreign Key Constraint on another table that is associated only with the Quarter, it should also include the other parts of the composite key. One possible solution to your problem it would be to declare other columns as your primary keys. Furthermore, I don't see the reason why you don't use a column with a auto generated INT (IDENTITY) as you primary key, or a column with an auto generated GUID.
If you are trying to INSERT a value into the table table_1 that does not exist in the table table_2 then you will get the error.
Since you have a Foreign Key on the table_1 field, you have to have the same value in the table_2.
See a non-working demo. This demo shows that the value for Codigo does not exist in the Fabricantes table so it throws the error message. Here is a working demo which shows that the value is in the table first.

MySQL Workbench; creating intermediate tables and mapping them

I have two tables in MySql Workbench; Categories and Products. I created a third table which contains PK's from the first two tables. How do I sort out which product falls under which category. Do I need to create a fourth table? Also, it is obvious that one category has many products. How do I put this data into the table?
The way I use it in my company is I have a Category table just as you have, a Brands table and a Products table (this is the basic setup, I have more tables for price history, product history, etc).
The Products table is the primary one, containing a foreign key to each of Categories and Brands.
Table tbl_category and tbl_brand has the same structure:
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(100) | YES | | NULL | |
+-------+--------------+------+-----+---------+----------------+
Table tbl_product:
+-----------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| category | int(11) | YES | MUL | NULL | |
| brand | int(11) | YES | MUL | NULL | |
| spec | varchar(100) | YES | | NULL | |
| descript | varchar(1000) | YES | | NULL | |
| unit | varchar(10) | YES | | PÇ | |
| cost | decimal(16,2) | YES | | NULL | |
| ..................................................................|
| |
+-----------+---------------+------+-----+---------+----------------+
And I have the following foreign keys:
KEY `product_category_fk` (`category`),
KEY `product_brand_fk` (`brand`),
CONSTRAINT `product_brand_fk` FOREIGN KEY (`brand`) REFERENCES `tbl_brand` (`id`) ON UPDATE CASCADE,
CONSTRAINT `product_category_fk` FOREIGN KEY (`category`) REFERENCES `tbl_category` (`id`) ON UPDATE CASCADE
The reasoning I used is: any given product has only one category and one brand, so I can put that information in the product record itself.
This is a good example starting point (Taken from here)

Alter one Table on Insertion into another

Let me first explain my situation here , I have a tabled called users which stores the user information.
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| user_id | varchar(10) | NO | PRI | NULL | |
| username | text | NO | | NULL | |
| password | text | NO | | NULL | |
| name | text | NO | | NULL | |
| email | text | NO | | NULL | |
| status | varchar(15) | NO | | active | |
+----------+-------------+------+-----+---------+-------+
And a table called country
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| country_id | varchar(10) | NO | PRI | NULL | |
| country_name | text | NO | | NULL | |
| country_rate | double | NO | | 0.2 | |
+--------------+-------------+------+-----+---------+-------+
Now , I need a process to map the no of countries assigned a particular user.
For Example:
user_001 is allowed to use country_001 , country_002 , country_003 n
user_002 is allowed to use country_003 , country_008
and so on..
What is the best approach to achieve the above?
What I thought is to have a table called say assignment and it will have the following fields:
assignment_id (primary key)
user_id (foreign Key)
country_001 (bool)
country_002 (bool)
...
...
country_010 (bool)
I am not sure if this the best approach , but even if I go for this , I am stuck in the point as to how to alter the structure of assignment , on every insertion in the country table ( Add a BOOL field in assignment with the newly created country_id as the column name).
I hope I was able to explain m situation. I know I can achieve via language support code (PHP , C++ etc) , but I was thinking if this could be done using some kind of TRIGGER so that I don't have to care about it in the code section.
Thanks a Lot.
It would be a better option to define user_countries table like this:
user_id fk on users(user_id)
country_id fk on countries(country_id)
unique key on ( user_id, country_id ) -- composite unique key
I am not sure why you want to define 10 columns in the user-country relation table.
But instead of 10 columns you just define a country_id with a foreign key and
with one-to-many relation ship between user_id and country_id. With this you can easily handle user and country relations. Unique key on their combination makes data access faster and avoids duplicates too.