Create a one to many relationship in MySQL - mysql

I have two tables, one with arbitrary rows of books and one with specific rows of subjects and ids.
mysql> describe books;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| title | varchar(250) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
mysql> describe subjects;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| subjects | varchar(250) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
I want to associate one book to many subject ids, is there any array type I can use so that one column in my books table contains an array of different subject ids?
Can I then do a query on the books table to see which books are related to one subject?

This is a many-to-many relationship, not a one-to-many relationship. (A book can have several subjects, and a subject can have several books.)
The normative pattern is to implement a third table to store the "relationship" between books and subjects.
As an example of a minimal relationship table:
CREATE TABLE books_subjects
( subjects_id INT(11) NOT NULL COMMENT 'PK, FK ref subjects(id)'
, books_id INT(11) NOT NULL COMMENT 'PK, FK ref books(id)'
, PRIMARY KEY (subjects_id, books_id)
);
For InnoDB, we can define the foreign key constraints:
ALTER TABLE books_subjects ADD CONSTRAINT FK_books_subjects_books
FOREIGN KEY (books_id) REFERENCES books(id);
ALTER TABLE books_subjects ADD CONSTRAINT FK_books_subjects_subjects
FOREIGN KEY (subjects_id) REFERENCES subjects(id);
This effectively resolves the many-to-many relationship into two one-to-many relationships.
A books can have multiple books_subjects, but a books_subjects is related to exactly one books.
A subjects can have multiple books_subjects, but a books_subjects is related to exactly one subjects.
Queries to retrieve books related to a particular subjects would involve a JOIN operation to the books_subjects table.
For example, to find all books of particular subjects:
SELECT b.id
, b.title
FROM books b
JOIN books_subjects j
ON j.books_id = b.id
JOIN subjects s
ON s.id = j.subjects_id
WHERE s.subjects = 'Psychology'
ORDER BY b.title
To find books that don't have any subjects, e.g.
SELECT b.id
, b.title
FROM books b
LEFT
JOIN books_subjects j
ON j.books_id = b.id
WHERE j.books_id IS NULL
ORDER BY b.title
NOTE: I typically use singular names for tables. So, there would be a lot fewer "s" characters in my column names and foreign key names. I may have omitted an "s" or two in my typing.

Related

MySQL Adding Foreign Key Error 1215

I know such a question is asked before. I made sure that they have the same data type and also checked my syntax, but I am still getting the error:
ALTER TABLE meetings ADD FOREIGN KEY (ownerName) REFERENCES employees(name);
ERROR 1215 (HY000): Cannot add foreign key constraint
mysql> desc `meetings`;
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| room | int(6) | NO | | NULL | |
| ownerName | varchar(30) | NO | | NULL | |
| ownerID | varchar(30) | NO | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> desc `employees`;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| name | varchar(30) | NO | | NULL | |
| username | varchar(30) | NO | PRI | NULL | |
| pswd | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
What am I doing wrong?
name is not primary key in employees table so .. try using username
ALTER TABLE meetings ADD FOREIGN KEY (ownerName) REFERENCES employees(username);
or as suggested by DanielE or you can use the column name but need an UNIQUE index for this column
Change the primary key of 'employees' from user name to name. Then you can use
ALTER TABLE meetings ADD FOREIGN KEY (ownerName) REFERENCES employees(name);
What am I doing wrong? Error 1215 is probably the least of the problems here.
Some of the answers given for this question suggest making username the referenced column in employees rather than name, which is fine as far as it goes, but ignores some really fundamental issues in the schema and quite possibly wasn't the poster's intention for these columns. The rest of this answer is based on my own set of assumptions.
meetings table
Looking at the meetings table, I'm left wondering about the purpose of the ownerID column. Since the intention is to have ownerName as a foreign key to employees, what exactly is ownerID? The name suggests it also somehow references employees, but there is no id or ownerID in employees. Also, if any column starting owner... refers to an employee then why would you need both in the meetings table? One of them is surely redundant. Why is ownerID a VARCHAR(30)? ID columns tend to be INT. Of course, I may be reading to much into this and ownerID may have some other purpose that has nothing to do with an employee, but if that's the case the name is likely going to cause confusion in the future.
The meetings table also has an INT surrogate key in id. There's another INT for room. Since room isn't a foreign key, it suggests that rooms are either consistently identified only by number (which would be strange in my experience) and that there is nothing more to a 'room' that's worth capturing (e.g. location, capacity, equipment etc.) to bother with modelling data about the room in a separate table (again unlikely). Alternatively, room might itself be a foreign key referencing an INT id column in an, as yet undefined, rooms table.
employees table
If we accept ownerID as a more appropriate foreign key to the employee that owns the meeting (it uses less memory to index than either name or username) then consistency would suggest another surrogate key id as the primary key in the employees table. It's not necessary to do this, username would be unique and is fine on it's own, but it's simpler and more efficient. The other suggestion made that name should be the PK in employees is wrong - it presupposes that names are always unique.
A single column to cover an employee name would also be unusual.
The point made about referencing a PK or a unique index is well made (even if it's not strictly necessary in Innodb), I'd just say that ownerName is the wrong foreign key and username and name are the wrong references because there is a better alternative.
And, finally, is a NULL password (pswd) a good idea?

MySQL add dynamic amount to an table

i am very new to MySQL and making my first steps with it. I have right now 2 tables that look like this:
Event:
+-------+-------------+
| Field | Type |
+-------+-------------+
| id | int(11) |
| name | varchar(30) |
| date | datetime |
+-------+-------------+
User:
+----------+-------------+
| Field | Type |
+----------+-------------+
| name | varchar(30) |
| amount | int |
+----------+-------------+
Now i would like to be able to add an dynamic amount of Users to an Event but i am not sure how i should create an dynamic table for that or how otherwise i should implement such an behaviour. What would be the best practice?
Help would be appreciated.
That is a classic many-to-many relation. You handle it by adding a new table
event_users table
-----------------
event_id
user_id
For an event (id=1) having two users (id=3,4) it looks like this
event_id | user_id
1 | 3
1 | 4
To get all users of an event you would do this
select u.name
from users u
join event_users eu on eu.user_id = u.id
join events e on eu.event_id = e.id
where e.name = 'my event'
You need a third table, a junction table:
create table UserEvents (
UserEventId int auto_increment primary key,
UserId int,
EventId int,
constraint fk_userevents_userid foreign key (UserId) references Users(id),
constraint fk_userevents_eventid foreign key (EventId) references Events(id)
);
Notes:
I name tables in the plural.
The id for the table is the singular followed by "id" (so I prefer Users(UserId) and Events(EventId).
This has declared foreign key relationships.
If duplicates are not allowed, then you also want a unique constraint on (UserId, EventId).
For this you should use a third table in which you should maintain event and related user id.
user_event
id user_id event_id created_date
user_id and event_id are foreign keys for this table and will point primary keys of user and event table respectively

SQL auto_increment non-int-only values

I was looking into auto_increment and I would like to automatically generate unique id primary key. The problem is I only know how to do that with numerical ids. As far as I know, primary keys have to be unique. So I cant use numerical auto_increment for multiple tables because the id wont be unique. Here's how I do it now:
CREATE TABLE students (
id int(3) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name char(20),
lastname char(20)
);
To the best of my knowledge this will generate ids like: 1, 2, 3, 4 unless I name a different starting number. Would it be possible to autoi_increment something like "stud_1" or "stud_01" and then keep going increasing the number? I haven't come across such an example so far.
I wrote a lot in the comments above, but let me apply it to a pratical application in an answer. We'll begin with your table and then add two more:
Tables:
CREATE TABLE students (
id int(3) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name char(20),
lastname char(20)
) ENGINE = INNODB;
CREATE TABLE subjects (
id int(3) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name char(20)
) ENGINE = INNODB;
CREATE TABLE student_subject (
student_id int(3) NOT NULL,
subject_id int(3) NOT NULL,
PRIMARY KEY(student_id, subject_id)
FOREIGN KEY(student_id) references students(id),
FOREIGN KEY(subject_id) references subjects(id)
) ENGINE = INNODB;
With a setup of tables like this, it is possible to have multiple numerical auto_increment primary key columns within the same database. In this case, there are two students.id and subjects.id. The third table student_subject shows how to link these together where both student_id and subject_id are primary keys for that table but each is a foreign key of their entity table as well.
With a setup like this querying becomes simple:
Sample Queries:
/* Get All students */
SELECT
students.id,
students.name,
students.lastname
FROM students;
/* Get All Subjects */
SELECT
subjects.id,
subjects.name
FROM subjects;
/* Get all subjects for a particular student */
SELECT
subjects.name
FROM subjects
INNER JOIN student_subject ON subjects.id = student_subject.subject_id
INNER JOIN students ON student_subject.student_id = students.id
WHERE students.id = 1;
EDIT:
Below is some sample data that demonstrates the three table many-to-many concept that the above structure illustrates:
/* Students Table */
+----+----------------+----------------+
| id | name | lastname |
+----+----------------+----------------+
| 1 | John | Doe |
|----|----------------|----------------|
| 2 | Joe | Bob |
+----+----------------+----------------+
/* Subjects Table */
+----+----------------+
| id | name |
+----+----------------+
| 1 | Calculus |
|----|----------------|
| 2 | Physics |
|----|----------------|
| 3 | Geography |
+----+----------------+
/* Student Subject Table */
+------------+------------+
| student_id | subject_id |
+------------+------------+
| 1 | 1 |
|------------|------------|
| 1 | 2 |
|------------|------------|
| 2 | 2 |
|------------|------------|
| 2 | 3 |
+------------+------------+
With this table structure, each student can have many subjects and each subject can belong to many students. For example, John Doe (student 1) is enrolled in both Calculus (subject 1) and Physics (subject 2). Joe Bob (student 2) is enrolled in Geography (subject 3) and also in Physics (subject 2) with John Doe.
In this example, each student is enrolled in two subjects and both students are enrolled in one subject. With the example you linked to from w3schools.com, this many-to-many relationship would not be possible.
Primary key values only need to be unique within a table, so there's no reason you can't use simple auto-increment numeric ids for every table.
As far as the question - I don't believe there's a direct way to "auto-increment" a string value in the way you describe. You could set something up with triggers and a sequence (or regular auto-increment column) if you really wanted to.

Should the auto increment column be the primary key?

I'm confused as to how to assign primary keys.
For example, let's say I have these two tables:
users table where the user_id is unique:
+---------+----------+--------------+
| user_id | username | password |
+---------+----------+--------------+
| 1 | hello | somepassword |
| 2 | world | another |
| 3 | stack | overflow |
+---------+----------+--------------+
posts table where the post_id is unique:
+---------+---------+--------------+
| post_id | user_id | content |
+---------+---------+--------------+
| 1 | 1 | Hello World! |
| 2 | 1 | Another. |
| 3 | 3 | Number 3. |
| 4 | 2 | Stack. |
| 5 | 1 | Overflow. |
+---------+---------+--------------+
Obviously for the users table the primary key should be user_id, but what should the primary key be in the posts table? post_id or user_id? Please explain.
The primary key for the Posts table should also be the auto increment value, post_id, because it's the only thing that uniquely identifies each post, because each post has an id unlike any other. The user_id won't always be unique because the same user could have multiple posts (as far as I know) so it can't uniquely identify the posts. If you need to relate information between the tables you can always do a join on the user_id from both tables, however to identify things with a primary key, the post_id would be your best bet.
Surely, you have this sceneraio:
A user can post several posts.
A post can be posted, logically, by one user only.
Thus, you are dealing with a One-To-Many model.
Once these things are clear to you, you can guess that the primary key of users must appear as a foreign key in posts. This is what you obviously have done already.
Now, wether post_id is enough as the primary key of posts depends on the whole entity relationship model you have (how many other entities do you have and what are their relationship to each others).
However, you will not need, for this specific scenario to combine the foreign key user_id as a part of the primary key of posts.
Note: when you implement your tables, please add the constraints of auto_increment and not null to user_id and post_id.
Let's summerize all this mess in SQL:
Table users:
mysql> create table users (user_id int(2) unique auto_increment not null, username varchar(15) not null, password varchar(20) not null, primary key(user_id));Query OK, 0 rows affected (0.33 sec)
Table posts:
mysql> create table posts(post_id int(2) unique auto_increment not null, user_id int(2) not null, content varchar(50) not null, foreign key(user_id) references users(user_id), primary key(post_id));
Query OK, 0 rows affected (0.26 sec)
Of course it should be user_id because when you use ORM then it will map tables automatically from proper naming of keys
You may refer ORM here : Good PHP ORM Library?

StringInString Query?

I have this database where I have stored some tags in it.
I stored the tags like this:
"humor,funny,animal"
Now I need a mysql query that selects this line when I search for "humor", "funny" or "animal". What I have until now:
SELECT id FROM database WHERE tags REGEXP 'humor' LIMIT 1
Unfortunately, it does not work. Could someone of you please help me out?
Edit: Thanks for all the responses! I will now need to study this first! But problem solved :)
Short Term
Because the tags are stored as denormalized data, use the FIND_IN_SET function:
SELECT t.id
FROM YOUR_TABLE t
WHERE FIND_IN_SET('humour', t.tags) > 0
Long Term Solution
Setup the tables to properly handle a many-to-many relationship:
TAGS
tag_id (primary key)
tag_description
ITEMS
item_id (primary key)
ITEM_TAGS
item_id (primary key, foreign key to ITEMS.item_id)
tag_id (primary key, foreign key to TAGS.tag_id)
Making the two columns in ITEM_TAGS the primary key means you don't have to worry about duplicates. And yes, this means using the InnoDB engine...
Then, you can use:
SELECT i.item_id
FROM ITEMS i
WHERE EXISTS (SELECT NULL
FROM ITEM_TAGS it
JOIN TAGS t ON t.tag_id = it.tag_id
WHERE t.tag_description = 'humour'
AND it.item_id = i.item_id)
You can use LIKE
SELECT id FROM database WHERE tags LIKE '%humor%' LIMIT 1
Which will search for any entry where 'humor' is a substring. Note this will also return items tagged 'humorous'.
But like others said, having a separate table for tags would be best. To do this you will also need a pivot table.
So for example
-------------- data -------------
| ID | NAME |
| 1 | example |
| 2 | example 2 |
-----------------------------------
-------------- tags -------------
| ID | NAME |
| 1 | humor |
| 2 | cats |
| 3 | wumpus |
-----------------------------------
------------ data_tags ----------
| DATA_ID | TAG_ID |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 3 |
-----------------------------------
To expand on Tomalak's comment, this would be best solved using a many-to-many relationship for database to tag relationships. This involves adding two new tables. Something like this (forgive my rusty MySQL)
CREATE TABLE `Tag` (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
tag VARCHAR(64) NOT NULL,
PRIMARY KEY (id),
UNIQUE (tag)
) ENGINE=InnoDB;
CREATE TABLE `DatabaseTag` (
database_id INT(11) UNSIGNED NOT NULL, -- just guessing your database.id type here
tag_id INT(11) UNSIGNED NOT NULL,
PRIMARY KEY (database_id, tag_id),
FOREIGN KEY (database_id) REFERENCES `database` (id)
ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES `Tag` (id)
ON DELETE CASCADE
) ENGINE=InndoDB;
Then, to find all the database records matching tag "humour", your query would look like
SELECT id FROM `database` d
INNER JOIN `DatabaseTag` dt ON d.id = dt.database_id
INNER JOIN `Tag` t ON dt.tag_id = t.id
WHERE t.tag = 'humour'