4

This Is Not Homework. I have changed the names of the tables and fields, for illustrative purposes only. I admit that I am completely new to MySQL. Please consider that in your answer.

The best way to illustrate the function of the query I need is like this:

I have two tables.

One table has a 0..1 to 0..n relationship to the other table.

For Simplicities Sake Only, Suppose that the two tables were Recipe and Ingredient.

One of the fields in the Ingredient table refers to the Recipe table, but may be null.

Just For Example: alt text

I want to know the SQL for something like: How many recipes call for "Olives" in the amount of "1" AND "Mushrooms" in the amount of "2"

Being brand new to The Structured Query Language, I'm not even sure what to google for this information.

Am I on the right track with the following?:

SELECT COUNT(DISTINCT Recipe.ID) AS Answer FROM Recipe, Ingredient 
  WHERE Ingredient.RecipeID=Recipe.ID AND Ingredient.Name='Olives'
  AND Ingredient.Amount=1 AND Ingredient.Name='Mushrooms'
  AND Ingredient.Amount=2

I realize this is totally wrong because Name cannot be BOTH Olives And Mushrooms... but don't know what to put instead, since I need to count all recipes with both, but only all recipes with both.

How can I properly write such a query for MySQL?

Joshua
  • 6,643
  • 15
  • 55
  • 76

5 Answers5

3

You were close.

Try something like

SELECT  COUNT(Recipe.ID) Answer
FROM    Recipe INNER JOIN
        Ingredient olives ON olives.RecipeID=Recipe.ID INNER JOIN
        Ingredient mushrooms ON mushrooms.RecipeID=Recipe.ID 
WHERE   olives.Name='Olives'
AND     mushrooms.Name='Mushrooms'
AND     olives.Amount = 1
AND     mushrooms.Amount = 2

You can join to the same table twice, all you need to do is give the table an appropriate alias.

Adriaan Stander
  • 162,879
  • 31
  • 289
  • 284
  • Don't forget that strings in SQL use single quotes. – jocull Nov 19 '10 at 04:30
  • Won't work. There's no need to do 2 join, you're using the same condition. You're just joining the same data twice. – Vincent Savard Nov 19 '10 at 05:03
  • 1
    @Vincent Savard, I think you might have understood the question incorrectly? – Adriaan Stander Nov 19 '10 at 05:04
  • Quite possible at first, but I can't see how this would select the good data. Let's assume you have {'Ingredient 1', 1} where 1 is a reference to {1, 'Olive'}, even though you do two explicit joins, the tuple is going to end up as {'Ingredient 1', 1, 1, 'Olive', 1, 'Olive'}. The RecipeID doesn't change. – Vincent Savard Nov 19 '10 at 05:08
3

Use:

SELECT COUNT(r.id)
  FROM RECIPE r
 WHERE EXISTS(SELECT NULL
                FROM INGREDIENTS i
               WHERE i.recipeid = r.id
                 AND i.name = 'Olives'
                 AND i.amount = 1)
   AND EXISTS(SELECT NULL
                FROM INGREDIENTS i
               WHERE i.recipeid = r.id
                 AND i.name = 'Mushrooms'
                 AND i.amount = 2)
OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
  • Genuine question, is that more efficient that a simple join? – Vincent Savard Nov 19 '10 at 04:36
  • @Vincent Savard: JOINs aren't always the best approach -- one to many relationships can inflate the number of records returned, requiring distinct or GROUP BY clauses. Depending on the desired result, a GROUP BY can complicate things. – OMG Ponies Nov 19 '10 at 04:40
  • It seems like there are so many different ways to do this. Since I'm just starting out, I would hate to develop any bad habits. How should I go about selecting the best approach? What are the pro's and cons of your answer over that of ajreal? – Joshua Nov 19 '10 at 05:00
  • @Joshua: I'd recommend astanders answer - don't use OR if you don't have to. – OMG Ponies Nov 19 '10 at 05:01
  • I dint know "select null.." works in an exists clause - always assumed it has tob e "select 1.." at least. – Roopesh Shenoy Nov 19 '10 at 05:02
  • 1
    @Roopesh Shenoy: Actually, the SELECT clause is never evaluated in an EXISTS clause - try `EXISTS(SELECT 1/0)`. It should return an error for dividing by zero, but it won't. – OMG Ponies Nov 19 '10 at 05:04
  • I was wondering the same thing. So it *IS* Okay to "SELECT NULL" in an exists? – Joshua Nov 19 '10 at 05:08
  • @Joshua: You can always *test* it -- you're the one with the data. You are checking the answers people give you, right? – OMG Ponies Nov 19 '10 at 05:09
  • Of course, I just find my tests to be more meaningful if I have any clue what it is I'm doing and how it works. Right now, my big-picture understanding is really vague. The vast variety of the many different answers is somewhat overwhelming at my current level of SQL skill. – Joshua Nov 19 '10 at 05:11
1
SELECT COUNT(DISTINCT Recipe.ID) AS Answer 
FROM Recipe, Ingredient as ing1, Ingredient as ing2
WHERE 
  Ing1.RecipeID=Recipe.ID AND Ing1.Name="Olives" AND ing1.Amount=1 AND 
  Ing2.RecipeID=Recipe.ID AND Ing2.Name="Mushrooms" AND ing2.Amount=2;

Hope this help

ajreal
  • 46,720
  • 11
  • 89
  • 119
  • Don't forget that strings in SQL use single quotes. – jocull Nov 19 '10 at 04:32
  • This won't work. Even if you join two tables, there will never be one column equal to Olives AND one column equal to Mushrooms in the same row. Anyway, you don't need two joins to do what OP want. – Vincent Savard Nov 19 '10 at 04:42
  • Of all the answers, this one makes the most sense to me. Do you know how it compares to :JOIN" in efficiency? – Joshua Nov 19 '10 at 04:43
  • @Joshua : As I said in my answer, both syntaxes create the same query plan and there won't be any performance impact. It's just... _ugly_. – Vincent Savard Nov 19 '10 at 04:46
  • @Joshua: JOIN is ANSI-92 syntax; this is ANSI-89 JOIN syntax. – OMG Ponies Nov 19 '10 at 04:46
  • Sigh, in terms of performance, I guess not much diff. But is just ugly to read. http://stackoverflow.com/questions/334201/why-isnt-sql-ansi-92-standard-better-adopted-over-ansi-89 – ajreal Nov 19 '10 at 04:52
  • @Joshua: It won't. But you can just try it. – Vincent Savard Nov 19 '10 at 04:52
0

First of all, you use the old join syntax. INNER JOIN creates the same execution plan, but is much more clear. Then, your query is all about a plain, old condition. You just have to write them correctly! (It takes some practice, I agree.)

SELECT COUNT(DISTINCT R.ID) Answer
FROM Recipe R
INNER JOIN Ingredient I
    ON R.ID = I.RecipeID
    AND (R.Name = 'Olives' AND I.Amount = 1)
    OR (R.Name = 'Mushrooms' AND I.Amount = 2);

Here is my sample data :

mysql> SELECT * FROM Ingredient;
+------+------+--------+----------+
| ID   | Name | Amount | RecipeID |
+------+------+--------+----------+
|    1 | I1   |      1 |        1 |
|    1 | I2   |      2 |        1 |
|    1 | I3   |      2 |        1 |
|    1 | I4   |      3 |        1 |
|    1 | I1   |      1 |        2 |
|    1 | I2   |      1 |        2 |
|    1 | I3   |      3 |        2 |
|    1 | I4   |      2 |        2 |
|    1 | I1   |      2 |        3 |
|    1 | I2   |      1 |        3 |
+------+------+--------+----------+
10 rows in set (0.00 sec)

mysql> SELECT * FROM Recipe;
+------+-----------+
| ID   | Name      |
+------+-----------+
|    1 | Mushrooms |
|    2 | Olives    |
|    3 | Tomatoes  |
+------+-----------+
3 rows in set (0.00 sec)

And my query outputs 2, as it should.

Edit: Actually, I realised my query selected wrong rows. This works correctly :

SELECT COUNT(DISTINCT R.Id) Answer 
FROM Recipe R 
INNER JOIN Ingredient I 
    ON R.ID = I.RecipeID 
WHERE 
    (R.Name = 'Mushrooms' AND I.Amount = 2) 
    OR (R.Name = 'Olives' AND I.Amount = 1);

Which outputs 2 as well.

Vincent Savard
  • 34,979
  • 10
  • 68
  • 73
  • Why have you said "OR" on the last line of your query? – Joshua Nov 19 '10 at 04:53
  • Because it's either Olive + amount of 1 OR Mushrooms + amount of 2. You can replace the COUNT by * so you can see what is actually getting selected. – Vincent Savard Nov 19 '10 at 04:54
  • But I want recipies that contain Olives AND Mushrooms. I'm sorry. I guess I'm just not advanced enough to understand this approach. – Joshua Nov 19 '10 at 05:02
  • @Joshua : Ah, my bad, in this case OMG Ponies's solution is most likely the best. I thought you wanted the number of recipes where you had one of those, not both. – Vincent Savard Nov 19 '10 at 05:09
0

Just for reference...

How many recipes call for "Olives" in the amount of "1" AND "Mushrooms" in the amount of "2"

Looking at your table structure above, you may want an associative entity to go between recipes and ingredients. This makes it a lot easier to do the kind of query you're looking for with an inner join.

In between your two tables, I would imagine something like this...

Recipe_Ingredient
-----------------
(PK, FK) RecipeID
(PK, FK) IngredientID

If you have that, then each recipe may have many ingredients, and each ingredient may be a part of many recipes. Once you have a table like that to properly associate your two tables, you can join them together and get a complete recipe. For a more complicated query like this where you have separate conditions for two recipes, I would probably sub-query it to help understanding.

SELECT SUM(RecipeCount) as RecipeCount
FROM
(
    SELECT COUNT(r.*) as RecipeCount
    FROM Recipe r
    INNER JOIN Recipe_Ingredient ri on r.ID = ri.RecipeID
    INNER JOIN Ingredient i on i.ID = ri.IngredientID
    WHERE i.Name = 'Olives' AND i.Amount = 1
UNION ALL
    SELECT COUNT(r.*) as RecipeCount
    FROM Recipe r
    INNER JOIN Recipe_Ingredient ri on r.ID = ri.RecipeID
    INNER JOIN Ingredient i on i.ID = ri.IngredientID
    WHERE i.Name = 'Mushrooms' AND i.Amount = 2
) as subTable
jocull
  • 20,008
  • 22
  • 105
  • 149
  • While completely clear to me - and fitting with the context of my example, it was indeed only an example, and such a many-to-many relationship is inappropriate for my actual task which involves English Language grammar parsing. – Joshua Nov 19 '10 at 04:41
  • Understandable! I just wanted to throw it out there. There's lots of other good answers to choose from but no one brought this up. – jocull Nov 19 '10 at 04:45
  • You added a many-to-many table when all you need is a type code for ingredients... then you use a UNION while will return a unique list of count values -- if both are five, you'll only get one value of 5. Of which, there's nothing ensuring that the list of those with olives includes mushrooms or vice versa. This is **not** an answer to consider using. – OMG Ponies Nov 19 '10 at 05:23
  • You are right about the union, my mistake. I think a union all should correct that. I'm not sure I understand your bit about the type code though. Could you clarify how you would give a recipe multiple ingredients without repeating the ingredient data? – jocull Nov 19 '10 at 05:44