Joins on MySQL many-to-many tables - mysql

This has been driving me mad.
I have three tables:
items
ID
name
type
cats
ID
name
items_to_cats
FK_ITEM_ID
FK_CAT_ID
This is a simple many-to-many relationship. I have items and categories. Each item can be linked to one or more categories. This is done via a simple joining table where each row maintains a relationship between one item and one category using foreign key constraints.
You will notice that my "items" table has a field called "type". This is an indexed column that defines the type of content stored there. Example values here are "report", "interview", "opinion", etc.
Here's the question. I want to retrieve a list of categories that have at least one item of type "report".
Ideally I want to get the result in a single query using joins. Help!

select distinct cats.id, cats.name
from cats
join items_to_cats on items_to_cats.fk_cat_id=cats.id
join items on items.id=items_to_cats.fk_item_id
where items.type='report'
Just as a point of database design, if you have a small set of legal values for items.type, i.e. "report", "interview", "opinion", maybe a couple more, then you really should create a separate table for that with, say, an id and a name, then just put the type id in the items table. That way you don't get into trouble because somewhere it's mis-spelled "raport", or even more likely, someone puts "reports" instead of "report".

or how about this :
SELECT c.id, c.name
FROM cats c
WHERE c.id IN
(SELECT ic.fk_cat_id
FROM items_to_cats ic
JOIN items i on i.id=ic.fk_item_id
WHERE items.type='report'
)

Related

How to select comma-separated values from a field in one table joined to another table with a specific where condition?

I'm working on a mysql database select and cannot find a solution for this tricky problem.
There's one table "words" with id and names of objects (in this case possible objects in a picture).
words
ID object
house
tree
car
…
In the other table "pictures" all the information to a picture is saved. Besides to information to resolution, etc. there are especially informations on the objects in the picture. They are saved in the column objects by the ids from the table words like 1,5,122,345, etc.
Also the table pictures has a column "location", where the id of the place is written, where I took the picture.
pictures
location objectsinpicture ...
1 - 1,2,3,4
2 - 1,5,122,34
1 - 50,122,345
1 - 91,35,122,345
2 - 1,14,32
1 - 1,5,122,345
To tag new pictures of a particular place I want to become suggestions of already saved information. So I can create buttons in php to update the database instead of using a dropdown with multiple select.
What I have tried so far is the following:
SELECT words.id, words.object
FROM words, pictures
WHERE location = 2 AND FIND_IN_SET(words.id, pictures.objectsinpicture)
GROUP BY words.id
ORDER BY words.id
This nearly shows the expected values. But some information is missing. It doesn't show all the possible objects and I cannot find any reason for this.
What I want is for example all ids fo location 2 joined to the table words and to group double entries of objectsinpicture:
1,5,122,34
1,14,32
1,5,14,32,34,122
house
...
...
...
...
...
Maybe I need to use group_concat with comma separator. But this doesn't work, either. The problem seems to be where condition with the location.
I hope that anyone has an idea of solving this request.
Thanks in advance for any support!!!
This is a classic problem of denormalization causing problems.
What you need to do is store each object/picture association separately, in another table:
create table objectsinpicture (
picture_id int,
object_id int,
primary key (picture_id, object_id)
);
Instead of storing a comma-separated list, you would store one association per row in this table. It will grow to a large number of rows of course, but each row is just a pair of id's so the total size won't be too great.
Then you can query:
SELECT w.id, w.object
FROM pictures AS p
JOIN objectsinpicture AS o ON o.picture_id = p.id
JOIN words AS w ON o.object_id = w.id
WHERE p.location = 2;

How do I join all rows with the same name in one table using MYSQL?

Suppose I have a database called clubmembership that has a column for names, a column for clubs, and a column for the role they play in that club. The name Margrit would be in the column name many times, or as many times as she is in a club. If I want to see which people are members of the sewing club my query might look something like this:
SELECT DISTINCT NAME FROM CLUBMEMBERSHIP
WHERE CLUB=’SEWING’
AND ROLE=’MEMBER’;
My problem is that I can't figure out a query for who is not in the sewing club. Of course the simple 'not in' clause isn't working because there are plenty of rows which sewing does not appear in. In this database if someone is not in the sewing club, sewing does not appear under club so I imagine there is a way to join the different rows with the same name under 'name' and then potentially use the 'not in' clause
I hope this was a good explanation of this question. I have been struggling with this problem for a while now.
Thanks for your help!
Nicolle
This is not something that can be solved by just changing the existing code, it is to do with the database design.
Database normalisation is the process of sorting out your database into sensible tables.
If you’re adding a person many times, then you should create a table called members instead. And if there is a list of clubs, then you should create a clubs table.
Then, you can create a table to join them together.
Here’s your three tables:
members
-------
id (int)
name (varchar)
clubs
-------
id (int)
name (varchar)
memberships
-------
member_id (int)
club_id (int)
Then you can use joins in MySQL to return the information you need.
Stack Overflow doesn’t like external links as the answer should be here, but this is a huge topic that won’t fit in a single reply, so I would briefly read about database normalization, and then read about ‘joining’ tables.
If I understand you correctly, you wanted to list all names that is not a member of SEWING. The Inner query will get all Names that are member of SEWING, however, the NOT EXISTS operator will get all Names that are not found in the inner query.
SELECT DISTINCT C.NAME
FROM CLUBMEMBERSHIP C
WHERE C.ROLE = 'MEMBER'
NOT EXISTS
(
SELECT NULL
FROM CLUBMEMBERSHIP D
WHERE D.CLUB='SEWING'
AND D.ROLE='MEMBER'
AND C.NAME = D.NAME
)
Here's a Demo.

SQL - How to insert records that has multiple values of the same column?

I'm creating a database based on Pokemon but I'm currently stumped on inserting Pokemon with different moves.
Each Pokemon has a move set, so not just one move, but many. However, as I attempt to insert the Pokemon with its variable-length amount of moves into the table, MySQL ignores the previous ones and only inserts the last move.
In short: how do I insert multiple records of the same Pokemon but with its different move?
[I guess a good similar real-world example would be a Person having multiple email addresses. How would I go about inserting that into a table?]
The problem is that you're implementing it as a one-to-one relationship, but what you have is a many-to-many relationship (each Pokemon has many moves, each move can be learned by many pokemon).
What you'd probably want to do is have 2 tables.
Table 1: Pokemon
ID, Name, Move1ID, Move2ID, Move3ID, Move4ID, Types etc.
Table 2: Moves
ID, Name, PP, Power, type etc.
Then you could use another table which contains all the join information between those 2 tables. You'd have multiple rows containing the same Pokemon ID and multiple rows containing the same Move ID, but the [Pokemon ID, Move ID] combination would be unique.
Table 3: PokemonMoves
PkID, MoveID
Then you could just do a join from the Pokemon table to the Moves table via this relationship table
SELECT *
FROM Pokemon AS p
LEFT JOIN PokemonMoves AS pm on p.ID = pm.PkID
LEFT JOIN Moves AS m ON m.ID = pm.MoveID
There are lots of posts on SO about many-to-many relationships, this looks like a good place to start: Many to many relationship?
Well, what do the tables look like? (and is their structure under your control?)
If you are constrained to a single "Email" field, the only way I see you can associate multiple email addresses with a single record(=person) is to treat the Email field as a comma (or whatever) delimited list.
If you control the structure however, you can switch to a one-to-many relationship between "Person"s and "Email"s - something like:
tblPerson
[id]
tblEmailAddresses
[person_id]
[email]
You'd query that like this:
SELECT id, email
FROM tblPerson INNER JOIN tblEmailAddresses ON
id = person_id
WHERE id = <person you're interested in>
Which would return as many records as that person has email addresses.
Hard to say exactly how the insert would look without seeing your code/data, but you could do something like:
sID = <whatever>
For each sEmail in EmailCollection
INSERT INTO tblEmailAddresses
(person_id, email)
VALUES (sID, sEmail)
Next

create MySQL View with categories table

i have a bit of a problem, i have two MySQL tables namely products and categories. The categories table has an id as the primary key and name of the category. The category id is a foreign key in the products table. I want to create a view, but i don't want the view to contain the id as the category,instead i want the category name to be shown in the view. I have tried every kind of JOIN but I'm still not getting the required result. What am i doing wrong? here is my latest join. Which is pretty much the closest to what i want.
SELECT c.title as title,ct.name as category, c.picture as picture
FROM companies c left join categories ct on ct.id=c.category
I have successfully gotten the title and picture, but the category remains null.
Thanks for the replies.. Michael turns out that you were right , I mixed up the tables when performing the insert via my web interface so there wasn't a match. Thanks a lot!

SQL Inner Join With Multiple Columns

I've got 2 tables - dishes and ingredients:
in Dishes, I've got a list of pizza dishes, ordered as such:
In Ingredients, I've got a list of all the different ingredients for all the dishes, ordered as such:
I want to be able to list all the names of all the ingredients of each dish alongside each dish's name.
I've written this query that does not replace the ingredient ids with names as it should, instead opting to return an empty set - please explain what it that I'm doing wrong:
SELECT dishes.name, ingredients.name, ingredients.id
FROM dishes
INNER JOIN ingredients
ON dishes.ingredient_1=ingredients.id,dishes.ingredient_2=ingredients.id,dishes.ingredient_3=ingredients.id,dishes.ingredient_4=ingredients.id,dishes.ingredient_5=ingredients.id,dishes.ingredient_6=ingredients.id, dishes.ingredient_7=ingredients.id,dishes.ingredient_8=ingredients.id;
It would be great if you could refer to:
The logic of the DB structuring - am I doing it correctly?
The logic behind the SQL query - if the DB is built in the right fashion, then why upon executing the query I get the empty set?
If you've encountered such a problem before - one that requires a single-to-many relationship - how did you solved it in a way different than this, using PHP & MySQL?
Disregard The Text In Hebrew - Treat It As Your Own Language.
It seems to me that a better Database Structure would have a Dishes_Ingredients_Rel table, rather than having a bunch of columns for Ingredients.
DISHES_INGREDIENTS_REL
DishesID
IngredientID
Then, you could just do a much simpler JOIN.
SELECT Ingredients.Name
FROM Dishes_Ingredients_Rel
INNER JOIN Ingredients
ON Dishes_Ingredients.IngredientID = Ingredients.IngredientID
WHERE Dishes_Ingredients_Rel.DishesID = #DishesID
1. The logic of the DB structuring - am I doing it correctly?
This is denormalized data. To normalize it, you would restructure your database into three tables:
Pizza
PizzaIngredients
Ingredients
Pizza would have ID, name, and type where ID is the primary key.
PizzaIngredients would have PizzaId and IngredientId (this is a many-many table where the primary key is a composite key of PizzaId and IngredientID)
Ingredients has ID and name where ID is the primary key.
2. List all the names of all the ingredients of each dish alongside each dish's name. Something like this in MySQL (untested):
SELECT p.ID, p.name, GROUP_CONCAT(i.name) AS ingredients
FROM pizza p
INNER JOIN pizzaingredients pi ON p.ID = pi.PizzaID
INNER JOIN ingredients i ON pi.IngredientID = i.ID
GROUP BY p.id
3. If you've encountered such a problem before - one that requires a single-to-many relationship - how did you solved it in a way different than this, using PHP & MySQL?
Using a many-many relationship, since that what your example truly is. You have many pizzas which can have many ingredients. And many ingredients belong to many different pizzas.
The reason you are getting an empty result is because you are setting a join condition that never gets satisfied. During the INNER join execution the database engine compares each record of the first table with each record of the second one trying to find a match where the id of the ingredient table record being evaluated is equal to ingredient1 AND ingredient2 AND so on. It would return some result if you create a record in the first table with the same ingredient in all 8 columns (testing purposes only).
Regarding the database structure, you choose a denormalized one creating 8 columns for each ingredient. There are a lot of considerations possible on this data structure (performance, maintainability, or just think if you are asked to insert a dish with 9 ingredients for example) and I would personally go for a normalized data structure instead.
But if you want to keep this, you should write something like:
SELECT dishes.name, ingredients1.name, ingredients1.id, ingredients2.name, ingredients2.id, ...
FROM dishes
LEFT JOIN ingredients AS ingredients1 ON dishes.ingredient_1=ingredients1.id
LEFT JOIN ingredients AS ingredients2 ON dishes.ingredient_2=ingredients2.id
LEFT JOIN ingredients AS ingredients3 ON dishes.ingredient_3=ingredients3.id
...
The LEFT join is required to get a result for unmatched ingredients (0 value when no ingredient is set reading your example)