Getting unexpected results from SQL query - mysql

So I have 2 tables. 1 called "recipes" and one called "ingredients". In the recipes table I have: id, recipe_name, calories, bld (an enumerated list that can be B, L, or D for breakfast, lunch and dinner), prep (prep time), and skill (another enumerated list for beg, int, and adv).
In the ingredients table I have: recipe_id (with a foreign key constraint to the id column in the recipes table), and ingredient_name, and some other columns.
Im trying to select all of the columns from the recipes table and the ingredient_name comlumn from the ingredients table with some conditions - I want to return all recipes, even if they dont have ingredients in the ingredient table (this wouldnt really happen in practice but this is for me to learn anyway), and I want to exclude recipes if one of their ingredients includes part of a keyword the user enters (in the next example ill be using "olive" as the keyword).
Here's my SQL so far:
SELECT recipes.*, ingredients.ingredient_name
FROM recipes
LEFT OUTER JOIN ingredients
ON ingredients.recipe_id=recipes.id
WHERE ingredients.ingredient_name <> '%olive%'
ORDER BY RAND() LIMIT 7
This only returns the recipes with ingredients and is still including recipes where "olive" is part of one of the names of the ingredients.
Thanks for any help

It's best, when working with this type of logic, to think in sets of data.
Set 1: List of recipes where Olive is in the ingredients:
SELECT recipe_id
FROM ingredients
WHERE ingredient_name LIKE '%olive%'
Now we have a set of recipes that we can exclude because they have Olive in them.
SET 2: List of recipes and their ingredients:
SELECT recipes.*, ingredients.ingredient_name
FROM recipes
LEFT OUTER JOIN ingredients
ON ingredients.recipe_id=recipes.id
No surprises here. Just a set of everything. What we want to do is restrict this set, by the set in the previous step:
SET 3: Restrict SET2 set with SET1 set:
SELECT recipes.*, ingredients.ingredient_name
FROM recipes
LEFT OUTER JOIN ingredients
ON ingredients.recipe_id=recipes.id
WHERE recipes.id NOT IN
(
SELECT recipe_id
FROM ingredients
WHERE ingredient_name LIKE '%olive%'
)

Related

How to join two queries based on fields they have in common?

I am trying to solve this problem. I'll illustrate it with how fruits relate to recipes.
Background Info:
there are explicit links between fruits and recipes (very few values 1:1)
implicit links (same metadata between fruit and recipe) which could be 1:m
Fruit: Texture/Color
Recipe: Texture/Color.../Meal_Type
Essentially for the fruit aspect I have a query that joins several tables (ex.10), let's call this the fruit table.
For the recipe table, I have a query that joins a few table (ex.6).
I want to see for a fruit, how many recipes are possible. So Essentially, I want to join these 2 queries based on the texture/color fields to see the possible meal types. A fruit could have multiple recipes. I'm trying to use texture/color as a foreign key as if a fruit has the same metadata texture/color it should be linked.
Ex.
example logic
In this example, for the soft red fruit, 3 meals are possible: breakfast, lunch and a snack. For a hard orange fruit, 2 meals are possible: lunch and dessert.
How would I do this?
I tried to create a temporary table/view but I only have readonly access so I need another way.
First, we can query the associated records from the Fruit and Recipe tables using a SQL JOIN on the custom condition or relationship as described (match fruits with recipes having the same color and texture):
SELECT *
FROM Fruit f
JOIN Recipe r
ON f.Texture = r.Texture
AND f.Color = r.Color
This will return a result set containing a row for each matching pair of records, containing all columns from both tables. Each row will be a matching combination of Fruit and Recipe.
If we simply want to count how many unique meal times (how many distinct Meal_Types) for each fruit, we can select an aggregate count as follows:
SELECT f.ID, COUNT(DISTINCT r.Meal_Type) As Meals
FROM Fruit f
JOIN Recipe r
ON f.Texture = r.Texture
AND f.Color = r.Color
GROUP BY f.ID
The above query will return a result set containing a row for each fruit and the number of unique Meal_Types counted for that particular fruit. We use DISTINCT to only count each Meal_Type once, and GROUP BY f.ID to distinguish the individual counts (in our case, we're counting Meal_Type by Fruit.ID).

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)

How do i include multiple row reference in one row?

I have table pizza. It includes fields like cost, id, name (of pizza), and ingredients.
I also have table ingredients, with name of ingredient, and id. How to i put in table pizza ingedients for example 1,2,4,15, and be able to get that ingredients name from table ingredients?
Table Pizza:
id name cost ingredients
1 Vegie Pizza 12,59 1,2
Table Ingredients
id name
1 cheese
2 broccoli
3 pepperoni
I would like to get for egzample name and ingredients:
Vegie Piza - cheese, broccoli - 12,59
Or be able to order Vegie Pizza with pepperoni.
How do i connect this two tables in a way I described?
First of all donot store relations as comma separated values instead use junction table to relate 2 entities for this see Database Normalization
For your current solution you need to use find_in_set in join condition
select p.id,group_concat(i.name) ingredients,p.cost
from
Pizza p
join Ingredients i on(find_in_set(i.id,p.ingredients) > 0)
group by p.id
Fiddle Demo
You should have a table PizzaIngredients, with one row per pizza and per ingredient in the table. Someones, one doesn't have control over the data structure being used. If so, there is a solution in MySQL:
select p.name, p.cost, group_concat(i.name)
from pizza p join
ingredients i
on find_in_set(i.id, p.ingredients) > 0
group by p.name, p.cost;
However, a junction/association table is a much better way to store such data in a relational database.

How to in MySQL order rows by number of matches in associative table?

I apologize, because I really didn't know how to correctly form my question, so I will explain my problem:
I have 3 simple MySQL tables:
table recipes with column id and many others which are not important for this problem
table ingredients with 2 columns: id, name
associative table recipes_ingredients with 2 ID columns id_recipes and id_ingredients
Now I want user to search for recipes by their ingredients. User can specify one or more ingredients at a time. I know how to write SQL for the case when I want recipes that match ALL user's ingredients or recipes that match AT LEAST ONE of the ingredients.
But my question is, how do I get recipes ordered so that the first one is the one with most ingredients matching ingredients searched for by user.
I have been thinking about this all day but I have not come up with any ideas. Thanks for any help or hints in advance.
EDIT: My condition for the SQL query is, that I want all the recipes that have AT LEAST one ingredient in recipes_ingredients table, but I want those matching recipes ordered by number of matches of searched ingredients.
For example: User searches for 3 ingredients. There are 10 recipes that have at least one of these ingredients present. But I want to sort them, so first recipe would be the one that possibly matches all 3 searched ingredients. Then would be recipes that match only 2 ingredients and last will be recipes with only one matching ingredient.
Unverified:
select r.id, count(i.id) as ing_cnt
from recipes r
left join recipes_ingredients ri on ri.id_recipes = r.id
left join ingredients i on ri.id_ingredients = i.id
group by r.id
order by ing_cnt desc

Issue with Mysql View and/or different method?

I'm trying to make a mysql view to then be used with Sphinx search.
I have three tables: recipes, tags, ingredients.
*Bold Words are the row names
Recipes is where I store the recipe name, and the cooking time (durationtotal)
Ingredients is where I store 1 ingredient needed for the specific recipe (which means there is more than 1 ingredient corresponding to every recipe.) Ingredients has a recipe_id which corresponds to a recipe, and ingredient field where I store the name of the ingredient.
Tags is similar to Ingredients, in that for every recipe, there can be more than 1 tag. For example: Mom's Chicken Recipe has the tags(comma-delineated): yummy,chicken,thanksgiving Tags also has a recipe_id field and a tag field which is where I store the name of the tag.
Now, I'm implementing Sphinx Search and need a way to combine the
Recipe Id (recipes.id)
Recipe Name (recipes.name)
Ingredients Id (ingredients.id)
Ingredients Name (ingredients.ingredient)
Ingredients Corresponding Recipe Id (ingredients.recipe_id)
Tags Id (tags.id)
Tags Name (tags.tag)
Tags Corresponding Recipe Id (tags.recipe_id)
My thought process (I'm a pretty programmer, and just discovered Sphinx) is to create a MYSQL view that combines all of the above rows so I'd have a view that looked like this:
RecipeName: To' Good Chicken Soup......Tag:Yummy......Ingredient:Chicken
RecipeName: To' Good Chicken Soup......Tag:Delicious......Ingredient:Chicken
RecipeName: To' Good Chicken Soup......Tag:Soup......Ingredient:Chicken
RecipeName: To' Good Chicken Soup......Tag:Yummy......Ingredient:Water
The problem with this method is, as you can see, that it duplicates a lot of rows. But since this was only the method I could think of, I went ahead and created a new view with this:
SELECT
recipes.id,
recipes.name,
ingredients.ingredient,
tags.tag
FROM
recipes, ingredients, tags
WHERE
recipes.id=ingredients.recipe_id
AND recipes.id=tags.recipe_id;
I worked this code into mysql, but am only getting recipes which have tags/ingredients corresponding to them? Also, is this the right method to structure my mysql view?
Thanks for any and all help!
If you want to get all recipes regardless if they have ingredients or tags you want to use a LEFT OUTER JOIN
SELECT recipes.id, recipes.name, ingredients.ingredient, tags.tag
FROM recipes
LEFT OUTER JOIN ingredients ON recipes.id=ingredients.recipe_id
LEFT OUTER JOIN tags ON recipes.id=tags.recipe_id;
Try a LEFT JOIN:
http://www.w3schools.com/sql/sql_join_left.asp
SELECT
recipes.id,
recipes.name,
ingredients.ingredient,
tags.tag
FROM
recipes
LEFT JOIN ingredients ON recipes.id=ingredients.recipe_id
LEFT JOIN tags ON recipes.id=tags.recipe_id