Is it possible to give a whole table one specific ID? - mysql

New at this, so bear with me here..
I'm trying to create a playlist table which is comprised of songs from a song table.
So, I've got a basic setup like this.
songs
|song_id | title | artist |
| 1 | MyTitle | MyArtist |
That table would theoretically hold all of the available songs. And the playlists would be made from those available songs.
That table looks like
playlists
| id | playlist_name | playlist_songs |
But I'm not sure how to make the playlist_songs table. I assume it would look something simple like this:
playlist_songs
| id | song_id (foreign key) |
Where it's just a bunch of rows each referencing the song table. Problem with this is that I don't know how to "target" a specific table like that.
The other option would be storing all of the foreign song_ids "flatly" in a single field.
playlist songs
| id | song_ids |
| 1 | 1, 5, 4, 2, 11, 30, ...|
| 2 | 44, 43, 22, 1, 2, 40...|
However, everything I've read about databases thus far points to this being a poor solution, as it's not in normal form.. but.. I don't know how to put it in normal form!

First of all your playlist_songs should be a relation table:
playlist_songs
| playlist_id (foreign key) | song_id (foreign key) |
since a song can belong to more than one playlist and a playlist can have more than one song.
Then about your concern, the reference for a specific table you're looking for is ensured in the definition of the foreign key.
If you look closely in the syntax:
ALTER TABLE `playlist_songs`
ADD CONSTRAINT `playlist_songs_playlist` FOREIGN KEY (`playlist_id`) REFERENCES `playlist` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `playlist_songs_song` FOREIGN KEY (`song_id`) REFERENCES `song` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
the table is referenced along with the field to map the parents and children records.

Your three table structure is the correct way to do it. With a little deviation from what you had:
songs
|song_id | title | artist |
playlists
| playlist_id | playlist_name |
playlist_songs
| playlist_id | song_id |
Then to get the information for the songs in a particular playlist you would use (SQL Fiddle):
SELECT pl.playlist_name, s.artist, s.title
FROM playlists AS pl
JOIN playlist_songs AS pls ON pl.playlist_id = pls.playlist_id
JOIN songs AS s ON pls.song_id = s.song_id
WHERE pl.playlist_id = 1
You definitely do NOT want to: store all of the foreign song_ids "flatly" in a single field. It is better to normalize the data. But, just looking at that method, you would have to write unnecessary complex queries to parse out the songs from that field.

Related

Only allow column to have true once for each id?

I have this table:
ID | genre_id | is_best_in_genre | movie_name
--------------------------------
1 | 3 | 0 | Hateful Eight
2 | 3 | 0 | Django Unchained
2 | 3 | 1 | Inglorious B
2 | 3 | 0 | Once Upon A Time in Hollywood
is_best_in_genre can only be true (1) once for every genre_id. There can only be 1 best movie in each genre.
How would I make a constraint such as this?
Alternative idea:
The more proper, normalized way to handle this is probably to make a separate 'best_in_genre' table with a unique constraint on genre_id.
This is also easier to update, because you're not required to make sure that everything gets 0'd when selecting a new 'best'.
A better approach might be to "move" the column is_best_in_genre to a separate (joined) table. A simple table with two columns could do the job:
CREATE TABLE best (
idmovie int,
idgenre int,
PRIMARY KEY (idgenre))
This would need to be joined with the original table like:
SELECT m.*, CASE b.idmovie>0 THEN 1 ELSE 0 END best_in_genre
FROM movietable m
LEFT JOIN best b ON idmovie=id AND idgenre=genre_id
The PRIMARY KEY constraint in the table best will make sure that each genre can only appear once.

Is it possible to have a table linking to a table that is linking back to the first table using foreign keys?

Im playing around with MySQL at the moment, learning stuff about database design and wondered something i couldnt find an answer to in Google.
Imagine a table named 'products' with the primary key 'id' and two additional columns named 'name' and 'primary_image_id', where 'primary_image_id' is a foreign key linking to a second table.
The second table is named 'product_images' also with the primary key 'id' and two additional columns this time called 'path' (path to the image) and 'product_id'. 'product_id' is of course a foreign key linking back to the first table.
+----+-----------+------------------+
| id | name | primary_image_id |
+----+-----------+------------------+
| 1 | product_A | 3 |
+----+-----------+------------------+
| 2 | product_B | 6 |
+----+-----------+------------------+
+----+-----------+------------------+
| id | path | product_id |
+----+-----------+------------------+
| 1 | /image_01 | 2 |
+----+-----------+------------------+
| 2 | /image_02 | 1 |
+----+-----------+------------------+
| 3 | /image_03 | 1 |
+----+-----------+------------------+
| 4 | /image_04 | 1 |
+----+-----------+------------------+
| 5 | /image_05 | 2 |
+----+-----------+------------------+
| 6 | /image_06 | 2 |
+----+-----------+------------------+
The idea is to have a table with all product images while only one image per product is the preview image (primary image). Is this type of foreign key linking even possible? And if yes, is it good databse design or should I use an other method?
Thank you in advance!
This is a valid use case and the table design looks good if your intention is to just read data using foreign key like "Get all image paths for product id 1" or "Get primary image of product id 1" or "Get paths of all primary images".
People tend to avoid the cycle of foreign key reference in tables specially if there is a cascade dependency on delete/update events. You need to answer questions like "What should happen to image 2, 3 ,4 if product 1 is deleted" or "what should happen to product 1 if image 3 is deleted".
The answers would help you come with a design that fulfills your requirement
Just use indexes without FOREIGN KEYs.
A more typical approach would be to move the primary flag to the images table. Both of these approaches have the potential for illogical data —
Your way would allow product 1 to name image A as its primary while image A could identify product 2 as its product.
My way would allow products to have 0 or 2+ primary images if the flag wasn’t well-managed.
Depending on how worried you are about either inconsistency, you could try to manage it via triggers or constraints, although MySQL is a little lacking in these areas compared to other DBMSs.
One way to absolutely prevent a problem would be to have the primary flag in the images table, but use it as an int (rank), not a Boolean with a convention that minimum rank is the “primary” — create a unique index on the combination of (product ID, rank) — and access this data via a stored proc or view that could implement the rank convention for you, e.g. select * from images a where product_id = whatever and does not exist (select 1 from images b where a.product_id = b.product_id and a.rank > b.rank).
Seems like overkill, but you need to be the judge how important potential data integrity issues are for your application.

How can I delimit the possible values of a foreign key?

I have three tables in a MySQL DB.
This is the main table with organisation related stuff. Every Organisation has an unique identifier which is also the foreign key in some tables.
org
+------------+-------------+
| org_id | name |
+------------+-------------+
| 1 | a |
| 2 | b |
| 3 | c |
+------------+-------------+
This is the groups table. Organisations can have many groups.
groups
FOREIGN KEY (ORG_ID) REFERENCES ORG (ID);
+------------+-------------+----------+
| ID | org_id | name |
+------------+-------------+ ---------+
| 1 | 1 | Group1 |
| 2 | 2 | Group2 |
| 3 | 2 | Group3 |
+------------+-------------+----------+
And this is the feed table in which I would like to perform an update.
A feed can have only one associated group.
feed
FOREIGN KEY (GROUP_ID) REFERENCES GROUPS (ID);
+------------+-------------+--------------+
| ID | org_id | group_id |
+------------+-------------+ -------------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | NULL |
| 4 | 2 | 3 |
+------------+-------------+--------------+
So now there is one problem, that i can't solve. When I INSERT or UPDATE a row, I set the groups_id, but this can also be a groups_id which not belongs to the organisation.
This happens, because all ID's in GROUPS are valid FK values. That's a thing I want to avoid. It should only be possible to insert or update a row with a groups_id which has also the same org_id as in feeds.org_id.
As you can see, the data is now fine. But when I try to make this INSERT INTO feed VALUES (4, 2, 1) it were nice to see an error. Yeah, right, I'm missing an lovely error....
It is difficult for me to make an connection between them. There seems one information or method that I'm missing. I've been looking for a lot, but I don't know the words to describe my problem.
So I ask you, could you give me a tip?
EDIT:
All feeds and all groups are related to an organisation, which has an identifier. An organisation can create feeds/messages. When this feeds are not associated with a group, this feed ist public. For special feeds they can create a group. This group is related to this special organisation.
This works and everything is good:
UPDATE feed
SET title = "Title", message = "Message", groups_id = "1"
WHERE id = "1" AND org_id = "1"
But this works also:
UPDATE feed
SET title = "Title", message = "Message", groups_id = "2"
WHERE id = "1" AND org_id = "1"
The problem is, that it is possible to associate a group to a feed (which is associated to org 1), while the group is not associated with the org (group 2 is associated with org 2).
So my thought was, is there a way to solve this through FOREIGN KEY or similar (checks, joins, subqueries). Or should I think about my db design?
I think a composed foreign key solves your problem:
create table agroup (
id int primary key,
orgid int,
UNIQUE (id,orgid)
);
create table feed (
id int primary key,
groupid int,
orgid int,
FOREIGN KEY (groupid, orgid) REFERENCES agroup(id, orgid)
);
insert into agroup values (10, 1), (20, 1), (30, 2), (40, NULL);
insert into feed values (100,10,1), (101, 20, 1);
insert into feed values (102, 40, NULL); # works
insert into feed values (103, NULL, 1); # works as well
# insert into feed values (110,10,2); # yields error "Cannot add or update a child row: a foreign key constraint fails"
Note the UNIQUE(id,orgid), which seems to be necessary. Though I do not understand why agroup(id primary key) is not sufficient to make also agroup(id,orgid) unique, I got a compiler error without this explicit unique(id,orgid)-constraint. Documentation says that the referenced attributes must be indexed. Anyway, your problem should be solved.
EDIT: Extended example, which now demonstrates also the case of NULL-values in referencing attributes.
At least in MySQL, a composite foreign key constraint permits NULL values in the referencing (child) rows, regardless of whether the parent table contain rows with corresponding NULL-values or not. If one inserts a row with NULL-values for foreign-key attributes, the foreign key constraint is simply ignored. Confer mysql foreign key semantics, which says:
"... MySQL essentially implements the semantics defined by MATCH SIMPLE, which permit a foreign key to be all or partially NULL. In that case, the (child table) row containing such a foreign key is permitted to be inserted, and does not match any row in the referenced (parent) table. It is possible to implement other semantics using triggers."

How to add a N:M relationship to my MySQL symptoms and diseases database?

I am working on a health app and I've created a database (MySQL), which stores symptoms and diseases.
There should be a N:M relationship between the symptoms and diseases, so each symptom could have multiple diseases and vice versa. Symptoms should also have common and less common diseases and because of that, they should have a ranking (1 to infinity). How should I design/add this new feature?
Current table structure:
disease: d_id, d_name
symptom: s_id, s_name
You require an ordinary Associative table to resolve the logical n::m relationship. The PK of such tables is the two PKs of the parent tables. That gives the required row uniqueness, etc.
Second, it has data columns, so it is no longer an Associative table, it becomes an ordinary table, a binary relation (two parents).
I understand that in the symptoms-per-disease or diseases-per-symptom result sets, the rows will be ranked, but the column containing the value in the table is not rank. It is an indicator of weight given to each symptom::disease. The rank is the order in the result set, from 1 to no-of-rows, it is derived, it will change all the time. The weight is a stable value in the database, from 1 to infinity.
I recommend you give all the constraints names.
Could you show me a query example regarding the constraint names?
Sure. Simply use explicit CONSTRAINT clauses, after the columns, rather than placing each constraint with a single column. You have to do that in the case of compound Keys, such as this one. The naming convention I use for FK constraints is:
<parent>_<verb_phrase>_<child>_fk
The relevance of named constraints, as well as this naming convention, will become clear to you when you have many tables, when you are administering the various objects in the database.
The Verb Phrase comes from the data model (had the data been modelled), it describes the Action between the subject table and the object table.
In SQL the DDL looks like this (you will have to translate for your NONsql):
CREATE TABLE symptom_disease ( -- associative table
s_id INT NOT NULL,
d_id INT NOT NULL,
weight BIGINT NOT NULL, -- plus a data column
CONSTRAINT UC_PK -- named constraints
PRIMARY KEY ( s_id, d_id ),
CONSTRAINT symptom_indicates_symptom_disease_fk
FOREIGN KEY ( s_id )
REFERENCES symptom ( s_id ),
CONSTRAINT disease_is_indicated_by_symptom_disease_fk
FOREIGN KEY ( d_id )
REFERENCES disease ( d_id )
)
For the PK, you want U Unique and C Clustered. I have chosen ( s_id, d_id ) on the assumption that you will have more queries that look up diseases per symptom (than symptoms per disease). If the converse is true, change the clustering to ( d_id, s_id ).
You need another table, lets call it symptom_disease which shows the relation between a symptom and a disease. In another word each row of this table will show that a symptom is related to a disease with a specified rank. So look at this:
symptom_disease (s_id,d_id,rank);
In which s_id is foreign key to symptom.s_id and d_id is a foreign key to disease.d_id.
Lets have an example. Consider we have tow diseases like this:
| d_id | name |
| 1 | cold |
| 2 | cancer |
And tow symptoms:
| s_id | name |
| 1 |Stomach Ache|
| 2 | headache |
Now look at symptom_disease table:
| s_id | d_id | rank |
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 1 | 2 | 5 |
thees rows shows that stomach ache relates to cold by rank of 0 and to cancer by rank of 5; also headache relates to cold by rank of 0.

mySQL column to hold array

I'm a beginner concerning coding and especially SQL and PHP.
I deal with app. 120 users.
The users can acquire app. 300 different collectible items.
When a user acquires a specific item, I would like the ID number of that particular item to be stored in the row of the user who acquired it, so that there is some information about what items the user already has (and to avoid duplicate items in his possession).
Is there a good way to store such information?
Is it even possible to set a column type to array and store it there?
Please note: I'm not lazy and I've been digging around and searching for the answer for 2 hours. I couldn't find a solution. I know of the rule that one should insert only one piece of information into one cell.
MySQL does not support storing arrays. However, you can use a second table to emulate an array by storing the relation between the users and items. Say you have the table users:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
...
);
And you have a table defining items:
CREATE TABLE items (
item_id SERIAL PRIMARY KEY,
...
);
You can relate what items a user has using a table similar to user_items:
CREATE TABLE user_items (
id SERIAL PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
item_id BIGINT UNSIGNED NOT NULL,
...,
FOREIGN KEY (user_id)
REFERENCES users (user_id),
FOREIGN KEY (item_id)
REFERENCES items (item_id)
);
Then, to determine what items user 123 has acquired, you could use JOINs similar to:
SELECT items.*
FROM users
INNER JOIN user_items
ON user_items.user_id = users.user_id
INNER JOIN items
ON items.item_id = user_items.item_id
WHERE users.user_id = 123; -- Or some other condition.
I assume you have 2 tables for example, users and items. To control which user already has a specific item, i would create an associative table, including the UserID from users and ItemID from items. This way you can now check in your user_items table if the user already has this item.
Here is a small example:
users (UserID is PK):
+--------+----------+
| UserID | UserName |
+--------+----------+
| 1 | Fred |
| 2 | Joe |
+--------+----------+
items (ItemID is PK):
+---------+----------+
| ItemID | ItemName |
+---------+----------+
| 5 | Book |
| 6 | Computer |
+---------+----------+
user_items (ItemID referencing items.ItemID, UserID referencing users.UserID):
+---------+--------+
| ItemID | UserID |
+---------+--------+
| 5 | 1 |
| 6 | 2 |
+---------+--------+