SQL Database Model: FOOD and INGREDIENTS tables - sql

I have 2 tables: FOOD and INGREDIENTS. I want to select all ingredients that are necessary to make certain food.
I thought I could do something like this:
select NAME from INGREDIENTS
where ID in (select ING1_ID, ING2_ID, ING3_ID from FOOD where NAME = 'Meat soup')
That obviously doesn't work as I need to pass a string of comma separated IDs in the "in" sub-select query. I was looking into converting list of column values into a string and there are some solutions out there but I started to think that maybe I'm over-complicating this and my database model is wrong.
FOOD Table:
ID | NAME | ING1_ID | ING2_ID | ING3_ID
--------+---------------+----------+----------+--------
1 | Meat soup | 1 | 2 | 3
2 | Pasta Bolo | 3 | 4 | 5
3 | Chicken salad | 2 | 4 | 5
INGREDIENTS Table:
ID | NAME
--------+-----------
1 | pasta
2 | onion
3 | oil
4 | paprika
5 | chicken
6 | cucumber

Many dishes have more than three ingredients, and when you're searching, you don't care about whether a given ingredient is #1 or #2 or #3.
So a better model is
FOOD
id | name
1 | meat soup
INGREDIENTS
id | name
1 | meat
FOOD_INGREDIENTS
food_id | ingredients_id
1 | 1
Now your query is just
select i.name from ingredients i, food f, food_ingredients fi
where fi.food_id = f.id
and fi.ingredients_id = i.id
and f.name = "meat soup"
The "food_ingredients" table is often called a "join table". Another nice thing about a join table is that you can add information that's specific to the particular inclusion of an ingredient in a kind of food (such as amount, or how it should be prepared).

Your problems stem from the fact that you do not have a normalized data model.
You should redesign into something like this:
FOOD table:
FoodID
Name
INGREDIENTS table:
IngID
Name
FOOD2INGREDIENTS table:
FoodID
IngID
(adding quantity here would make a lot of sense)
Once such a redesign had been made you could get what you want simply by querying:
SELECT i.ID, I.Name
FROM Ingredients I
INNER JOIN Food2Ingredients F2I
ON I.IngID = F2I.IngID
WHERE
F2I.FoodID = :foodid;

As long as all your recipes are so impoverished as to require exactly three ingredients, then you can resolve the problem with the original query by writing:
SELECT Name FROM Ingredients
WHERE ID IN (SELECT ING1_ID FROM Food WHERE Name = 'Meat soup'
UNION
SELECT ING2_ID FROM Food WHERE Name = 'Meat soup'
UNION
SELECT ING3_ID FROM Food WHERE Name = 'Meat soup'
)
However, I think JacobM and Kerbocat are onto the right idea with the database redesign, and your sense that there was something wrong with your database design is to be commended. Note that if you need a recipe with 4 (or more) ingredients, you face a disaster with the current design (for example, because you have to edit all the existing SQL which includes multi-way UNION operations like the one shown above), but the redesigned schemas wouldn't even notice.

Related

Why is INNER JOIN producing more records than original file?

I have two tables. Table A & Table B. Table A has 40516 rows, and records sales by seller_id. The first column in Table A is the seller_id that repeats every time a sale is made.
Example: Table A (40516 rows)
seller_id | item | cost
------------------------
1 | dog | 5000
1 | cat | 50
4 |lizard| 80
5 |bird | 20
5 |fish | 90
The seller_id is also present in Table B, and also contains the corresponding name of the seller.
Example: Table B (5851 rows)
seller_id | seller_name
-------------------------
1 | Dog and Cat World INC
4 | Reptile Love.com
5 | Ocean Dogs Inc
I want to join these two tables, but only display the seller name from Table B and all other columns from Table A. When I do this with an INNER JOIN I get 40864 rows (348 extra rows). Shouldn't the query produce only the original 40516 rows?
Also not sure if this matters, but the seller_id can contain several zeros before the number (e.g., 0000845, 0000549).
I've looked around on here and haven't really found an answer. I've tried LEFT and RIGHT joins and get the same results for one and way more results for the other.
SQL Code Example:
SELECT public.table_B.seller_name, *
FROM public.table_A
INNER JOIN public.table_B ON public.table_A.seller_id =
public.table_B.seller_id;
Expected Results:
seller_name | seller_id | item | cost
------------------------------------------------
Dog and Cat World INC | 1 | dog | 5000
Dog and Cat World INC | 1 | cat | 50
Reptile Love.com | 4 |lizard| 80
Ocean Dogs Inc | 5 |bird | 20
Ocean Dogs Inc | 5 |fish | 90
I expected the results to contain the same number of rows in Table A. Instead I gut names matching up and an additional 348 rows...
Update:
I changed "unique_id" to "seller_id" in the question.
I guess I should have chosen a better name for unique_id in the original example. I didn't mean it to be unique in the sense of a key. It is just the seller's id that repeats every time there is a sale (in Table A). The seller's ID does repeat in Table A because it is supposed to. I simply want to pair up the seller IDs with the seller names.
Thanks again everyone for their help!
unique_id is already not correctly named in the first table, so there is no reason to assume it is unique in the second table either.
Run this query to find the duplicates:
select unique_id
from table_b
group by unique_id
having count(*) > 1;
You can fix the query using distinct on:
SELECT b.seller_name, a.*
FROM public.table_A a JOIN
(SELECT DISTINCT ON (b.unique_id) b.*
FROM public.table_B b
ORDER BY b.unique_id
) b
ON a.unique_id = b.unique_id;
In this case, you may get fewer records, if there are no matches. To fix that, use a LEFT JOIN.
Because unique id column is not unique.
Gordon Linoff was correct. The seller_id (formerly listed as unique_id) was indeed duplicated throughout the data set. I foolishly assumed otherwise. Also the seller_name had many duplicates too! In the end I had to use the CONCAT() function to join the seller_id with second identifier to create a type of foreign key. After I did this the join worked as expected. Thanks everyone!

Trying to find non-duplicate entries in mostly identical tables(access)

I have 2 different databases. They track different things about inventory. in essence they share 3 common fields. Location, item number and quantity. I've extracted these into 2 tables, with only those fields. Every time I find an answer, it doesn't get all the test cases, just some of the fields.
Items can be in multiple locations, and as a turn each location can have multiple items. The primary key would be location and item number.
I need to flag when an entry doesn't match all three fields.
I've only been able to find queries that match an ID or so, or who's queries are beyond my comprehension. in the below, I'd need a query that would show that rows 1,2, and 5 had issues. I'd run it on each table and have to verify it with a physical inventory.
Please refrain from commenting on it being silly having information in 2 different databases, All I get in response it to deal with it =P
Table A
Location ItemNum | QTY
-------------------------
1a1a | as1001 | 5
1a1b | as1003 | 10
1a1b | as1004 | 2
1a1c | as1005 | 15
1a1d | as1005 | 15
Table B
Location ItemNum | QTY
-------------------------
1a1a | as1001 | 10
1a1d | as1003 | 10
1a1b | as1004 | 2
1a1c | as1005 | 15
1a1e | as1005 | 15
This article seemed to do what I wanted but I couldn't get it to work.
To find entries in Table A that don't have an exactly matching entry in Table B:
select A.*
from A
left join B on A.location = B.location and A.ItemNum = B.ItemNum and A.qty = B.qty
where B.location Is Null
Just swap all the A's and B's to get the list of entries in B with no matching entry in A.

SQLite select specific product and the rest ordered

I have an issue. Suppose i have the table in Northwind database, where there are orders placed containing some products.
Order | Product
1 | Milk
1 | Cacao
1 | Juice
2 | Milk
2 | LemonJuice
2 | OrangeJuice
3 | Lemonade
3 | Remoulade
3 | GrapefruitJuice
Suppose Order is being placed and it contains FX Order 1 Milk, Cacao, Juice. Order 2 has Milk, LemonJuice, OrangeJuice.
I need to select all the orders which contains Milk ordered and to select the rest what have they ordered, SO If person in order 1 has ordered Milk, then i need to take cacao and juice as well. The same in order 2, I see Milk ordered, then i need to take LemonJuice and OrangeJuice, Whereas Order 3 does not contain Milk, So I do not need it.
How can i do that?
Trying for the second day, I am really in doubt of how to write it..
An embedded select would do it:
Select * from Orders where ord in
(select ord from Orders where Product = "Milk");
One thing I would like to point out is that "Order" is a keyword in SQLite, so you cannot name your column "Order". As you see in my code-snippet I renamed it to "ord". (The Table is called Orders and the second column is called (as in your description) Product.

MySQL query for initial filling of order column

Sorry for vague question title.
I've got a table containing huge list of, say, products, belonging to different categories. There's a foreign key column indicating which category that particular product belongs to. I.e. in "bananas" row category might be 3 which indicates "fruits".
Now I added additional column "order" which is for display order within that particular category. I need to do initial ordering. Since the list is big, I dont wanna change every row by hand. Is it possible to do with one or two queries? I dont care what initial order is as long as it starts with 1 and goes up.
I cant do something like SET order = id because id counts from 1 up regardless of product category and order must start anew from 1 up for every different category.
Example of what I need to achieve:
ID | product | category | Order
1 | bananas | fruits | 1
2 | chair | furniture | 1
3 | apples | fruits | 2
4 | cola | drinks | 1
5 | mango | fruits | 3
6 | pepsi | drinks | 2
(category is actually a number because it's foreign key, in example I put names just for clarification)
As you see, order numbers start anew from 1 for each different category.
Sounds like something a SQL procedure would be handy for.
Why not just set the order to the category? That is, why not:
update Table
set SortOrder = Category;
As an aside, you cannot have a column named order -- that is a reserved word in SQL.

SQL-Query needed to find distinct IDs probably using IN and NOT IN

Let's pretend I have a large recipe-database. A table for recipes each with an unique ID and a table for ingredients with a whole lot of sets like this:
ID | RECIPE | INGREDIENT
-------------------------------
1 | recipe_a | ingredient_a
2 | recipe_a | ingredient_b
3 | recipe_a | ingredient_c
4 | recipe_b | ingredient_a
5 | recipe_b | ingredient_d
6 | recipe_b | ingredient_e
Users can search for ingredients they want to see in their recipes and those they don't. The wanted query should be able to result in recipe_a when a user searches for recipes with ingredient_a and ingredient_b but not ingredient_d.
How would one do that in preferrably one query?
I tried the rather naive version with:
SELECT distinct recipe
from ingredients
where ingredient in (ingredient_a, ingredient_b)
and ingredient not in (ingredient_d)
This obv failed, because it still resulted in recipe_a and recipe_b, which it should do, because the rows 1 and 2 matched recipe_a and row 4 matched recipe_b.
Select Distinct ...
From Recipes As R
Where R.ingredient in(ingredient_a, ingredient_b...)
And Not Exists(
Select 1
From Recipes As R2
Where R2.Recipe = R.Recipe
And R2.Ingredient In(ingredient_d)
)
As Jeffrey L Whitledge mentioned, the above query will return any recipe that has at least one ingredient in the desired list and none in the undesired list. However, if you wanted to return recipes that contained all the ingredients in the desired list and none in the undesired list you could do:
Select Distinct ...
From Recipes As R
Where Exists (
Select 1
From Recipes As R2
Where R2.Recipe = R.Recipe
And R2.ingredient in(ingredient_a, ingredient_b...)
Having Count(*) = #CountOfPassedIngredients
)
And Not Exists(
Select 1
From Recipes As R2
Where R2.Recipe = R.Recipe
And R2.Ingredient In(ingredient_d)
)
In this scenario, you would need to have first determine the count of desired ingredients.