Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm using PostgreSQL. I have a table with 3 fields person, recipe and ingredient

person = creator of the recipe
recipe = the recipe
ingredient = one of the ingredients in the recipe

I want to create a query which results in every person who whenever has added carrot to a recipe, the person must also have added salt to the same recipe.

More than one person can have created the recipe, in which case the person who added the ingredient will be credited for adding the ingredient. Sometimes the ingredient is used more than once, even by the same person.

If this the table:

person1, rec1, carrot
person1, rec1, salt
person1, rec1, salt
person1, rec2, salt
person1, rec2, pepper
person2, rec1, carrot
person2, rec1, salt
person2, rec2, carrot
person2, rec2, pepper
person3, rec1, sugar
person3, rec1, carrot

Then I want this result: person1

Because this person is the only one who whenever has added carrot also have added salt.

"Nothing but the carrot could affect the result. I only want persons who has added at least one carrot in one of their recipes, but I don't want persons who have not also added salt to all of the same recipes they've added carrot to. Sorry, but I just can't explain it any clearer than that."

share|improve this question
    
So, to rephrase, we extract 'pers1' if count (pers1, recipe, carrot) <= count(pers1, recipe, salt) - or the ordering matters as well? –  raina77ow Oct 31 '12 at 17:04
    
person2 also created rec1 with carrot and salt. Who wins? –  Clodoaldo Neto Oct 31 '12 at 17:04
    
The order carrot and salt appear in is not important. We do not want persons that have added carrot to a recipe without adding salt. More than one person can add any ingredient to a recipe. –  Horse SMith Oct 31 '12 at 17:05
1  
but seeing you example person2, rec1, carrot; person2, rec1, salt has these records, so person will still appear? or is hat a typo on your example? –  John Woo Oct 31 '12 at 17:12
1  
The problem statement seems to be a bit ill-defined, so everyone's having trouble working out exactly what you want for the different cases. Try editing and elaborating with some more detail and explanation, covering the questions in the comments. –  Craig Ringer Oct 31 '12 at 23:44

3 Answers 3

How about this:

   SELECT DISTINCT person
     FROM tableName
    WHERE ingredient IN('carrot', 'salt')
 GROUP BY person, recipe
   HAVING SUM(CASE WHEN ingredient = 'carrot' THEN 1 ELSE -1 END) <= 0 
          AND
          COUNT(DISTINCT ingredient) > 1;

I admit I don't have much experience working with PostgreSql, but the query seems to give the results you require in this SQL Fiddle (credit to @JohnWoo for providing the one to begin from).

I have updated the answer; before it returned users with only salts in some of their recipes as legit ones. The second HAVING clause filters out such cases.

UPDATE: The previous query returned all the owners which have at least one recipe which follows the rule ("for each added carrot add salt also"). But you (seem to) actually need the ones with all the recipes following the rule. So the query looks like...

SELECT DISTINCT person 
  FROM tableName
 WHERE person NOT IN (   
     SELECT person
       FROM tableName
      WHERE ingredient IN('carrot', 'salt')
   GROUP BY person, recipe
     HAVING SUM(CASE WHEN ingredient = 'carrot' THEN 1 ELSE -1 END) > 0
 );

SQL Fiddle to play with.

share|improve this answer
    
I think you should remove the COUNT(DISTINCT ingredient) > 1 part. The OP (seems to) want those that have only salt in some of their recipes (or no salt and no carrot). –  ypercube Oct 31 '12 at 18:38
    
@ypercube: As I understand it, the OP wants to return only those persons who have at least one recipe with both salt and carrot, and who also have no recipes with carrot but without salt. (Recipes with salt but no carrot would not affect the person values returned.) –  Mark Bannister Oct 31 '12 at 18:44
1  
@Mark: You could be right. Lets see what OP means. A clarification would be nice. –  ypercube Oct 31 '12 at 18:59
    
Mark is right. Nothing but the carrot could affect the result. I only want persons who has at least one carrot in one of their recipes, but I don't want persons who have not also added salt to all of the same recipes they've added carrot to. Sorry, but I just can't explain it any clearer than that. Also, this is a very big table with lots of data, like several hundred thousand entries, so I'm pretty sure I shouldn't use NOT EXISTS to heavily. –  Horse SMith Nov 2 '12 at 1:57
    
@HorseSMith: So, have you tried the offered answers against your requirements? –  Mark Bannister Nov 2 '12 at 10:46

Try:

SELECT person from
(SELECT person, recipe, COUNT(DISTINCT ingredient) ingredients
 FROM tableName
 WHERE ingredient IN ('salt', 'carrot')
 GROUP BY person, recipe
 HAVING MAX(CASE WHEN ingredient = 'carrot' THEN 1 END) = 1) p
group by person
HAVING MIN(ingredients) = 2
share|improve this answer
    
Fails in this case (should not return person2) –  raina77ow Oct 31 '12 at 18:03
    
@raina77ow: in your cited case it should return person2 because rec1 has both salt and carrot while rec2 has neither salt nor carrot. Change the second 'person2', 'rec1', 'carrot' to 'person2', 'rec2', 'carrot' and person2 is excluded - like so: sqlfiddle.com/#!1/c8a87/1 –  Mark Bannister Oct 31 '12 at 18:07
    
"...in which case as long as he has added carrot he must also have added salt", citing the OP. I took it as 'the number of carrots in a single recipe added by a single user must be less than the number of salts in the same recipe added by the same user'. Otherwise the answer given by @JohnWoo is correct already, isn't it? –  raina77ow Oct 31 '12 at 18:11
    
@raina77ow: From the OP's comment on John Woo's answer: "The person may also have added salt or carrot more than once to the same recipe, in which case as long as he has added carrot he must also have added salt, but how many times does not matter, it is okay." John Woo's existing answer is not correct, because it will return person2 in the original example, when person2 should be excluded because their rec2 includes carrot but not salt. –  Mark Bannister Oct 31 '12 at 18:17

This seems a variation of the relational division problem.

A double-nested NOT EXISTS solution:

SELECT DISTINCT person
FROM tableName AS t
WHERE NOT EXISTS
      ( SELECT *
        FROM tableName AS chosen
        WHERE chosen.ingredient = 'carrot'
          AND chosen.person = t.person
          AND NOT EXISTS
              ( SELECT *
                FROM tableName AS required
                WHERE required.ingredient = 'salt'
                  AND required.recipe = chosen.recipe
                  AND required.person = chosen.person
              ) 
      ) ;

And a JOIN:

SELECT DISTINCT 
    t.person
FROM 
        tableName AS t
    LEFT JOIN
            tableName AS chosen
        LEFT JOIN
            tableName AS required
          ON  required.ingredient = 'salt'
          AND required.recipe = chosen.recipe
          AND required.person = chosen.person
      ON  chosen.ingredient = 'carrot'
      AND chosen.person = t.person
      AND required.ingredient IS NULL
WHERE
    chosen.ingredient IS NULL ;
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.