Should I use FOREIGN KEY or ADD INDEX in sql? - mysql

Today I have seen a video lecture in which they gave the foriegn key by using ADD INDEX on a table -
CASE 1 -
DECRIPTION OF TABLE 1 : subjects
+-----------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| menu_name | int(11) | YES | | NULL | |
| position | int(3) | YES | | NULL | |
| visible | tinyint(1) | YES | | NULL | |
+-----------+------------+------+-----+---------+----------------+
DECRIPTION OF TABLE 2 : pages
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| subject_id | int(11) | YES | | NULL | |
| menu_name | varchar(255) | YES | | NULL | |
| position | int(3) | YES | | NULL | |
| visible | tinyint(1) | YES | | NULL | |
| content | text | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
So in the column subject_id of table pages should store the id of table subjects.
Which one should i use and why ? -
ALTER TABLE pages ADD INDEX fk_subject_id (subject_id);
OR
ALTER TABLE pages
ADD FOREIGN KEY (subject_id) REFERENCES students(id);
video lecture uses ALTER TABLE pages ADD INDEX fk_subject_id (subject_id);.
CASE 2 -
Now Please cosider one more example -
According to above details, If I have 5 more tables including pages table(defined above).
All 5 tables have column subject_id which should store the data accodring to column id of table subjects.
So in this case
In this case, Which one Should I use ADD INDEX or FOREIGN KEY and why ?

Q : case 1 - Which one should i use ?
A : I'll choose fk not index,because the reference between pages and subjects is multi to one,if you add index on column with duplicate values that would not be helpful,because in most cases, only one index can be used to optimize a database query,and there's a primary index on subjects,so don't do that again.
note : you have to make pages - subject_id not null
Q : case 2 - Which one should i use ?
A : if the 5 tables between subjects are also multi to one,i'll choose FK not index,the reason like case1's answer.

Using a FOREIGN KEY and an INDEX are different things. FOREIGN KEYs are used for data integrity, so that you cannot have a reference, which points to nothing and that you cannot delete the "base" row without deleting the "linking" rows first (unless you use ON CASCADE DELETE stuff).
Indices are used to improve the search speed to find the correct rows faster in an SELECT and UPDATE query. This has nothing to do with data integrity.
To answer your question: You use a FOREIGN KEY if you want to reference the Id of rows from the other table (like you do with subject_id). Also, you don't need to add an INDEX on the column subject_id, because the InnoDB engine already does that.

Related

Only allow one row with "main" flag up in a table

I have a table as follows:
mysql> describe groups;
+--------------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| jobId | int(11) | NO | MUL | NULL | |
| name | varchar(45) | YES | | NULL | |
| notes | varchar(1024) | YES | | | |
| main | tinyint(1) | NO | | 0 | |
+--------------------+---------------+------+-----+---------+----------------+
There is another table, NOT relevant here, with jobs. This is the "child" table, 1:n for jobs. I want only ONE of these groups.
So, jobs will have several 'groups' entries; I want it so that only ONE of them, for a specific job, can be set as "1".
It's enforced in the code, but not in the DB right now.
Is it possible to enforce this at DB level?
You can create a unique index on the pair of columns:
UNIQUE KEY uix_1 (jobid, main)
The unique index permits multiple NULL values nullable columns so the pair (1, NULL) could be inserted multiple times but the pair (1, 1) could be inserted only once.
If you want to constrain the values for main column to 1 and NULL you have some options:
Use CHECK constraint – requires MySQL 8
Use ENUM('yes') – using numbers as enum values have gotchas
Create FOREIGN KEY (main) REFERENCES main_values(main)
Use BIT(1) datatype – this will permits 0 as well

How to add combined constraint on MySQL table?

I am creating a simple library management application where issued books data is captured in a table called 'transaction'. The structure of this table is as below where date_of_return can be null if the book is yet not returned by the member. Once the book is returned to library the date_of_return will be set to some datetime value and i.e. not null.
Since a book can be issued to only to one member at a time we should find atmost one row for a book_id where the date_of_return is null. No two rows for a given book_id with date_of_return is not null. How to specify this as a constraint? Because this seems to be of type unique constraint which is complex.
Is writing trigger the only way to solve this problem?
mysql> describe transaction;
+----------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| book_id | bigint(20) | NO | MUL | NULL | |
| member_id | bigint(20) | NO | MUL | NULL | |
| date_of_issue | datetime | NO | | NULL | |
| date_of_return | datetime | YES | | NULL | |
+----------------+------------+------+-----+---------+----------------+
you can create unqiue constraint for (book_id, date__of_return) so that if the book is not yet returned, no one can have the book. suppose that your table has this record
book_id ... . date_of_return
1 null
then there won't be any other record with same book_id and date_of_Return
ALTER TABLE transactions
ADD CONSTRAINT uc_transactions_book UNIQUE (book_id,date_of_return);
and since your date_of_Return is datetime, you wont have problem when the book is issued and returned multiple times in same day.

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.

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.

MySQL Alter table add column that is unique together with another column

I have such a table:
+---------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+-------------------+-----------------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| url | varchar(255) | YES | UNI | NULL | |
| ts | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| content | longblob | YES | | NULL | |
| source | varchar(255) | YES | | NULL | |
| state | int(11) | NO | | 0 | |
+---------+--------------+------+-----+-------------------+-----------------------------+
I'd like the id to stay the only PRIMARY KEY and I'd like to add field "VERSION" which will be unique.
What I want is to create unique pair (url, version) unique together but not separately. How can I do that? Should I add field version just like that, alter url so it's not unique and then add constraint?
Thanks in advance!
If what you're looking for is to store multiple versions of the same URL together in the table, then yes, what you need to do is:
Drop the unique constraint on URL
Add non-unique column version (assume integer here)
Create unique constraint or index on (url, version). I would suggest an index since I think that should make the unique checks faster.