Question

Étant donné les tableaux suivants:

Recipes
| id | name
| 1  | 'chocolate cream pie'
| 2  | 'banana cream pie'
| 3  | 'chocolate banana surprise'

Ingredients
| id | name
| 1  | 'banana'
| 2  | 'cream'
| 3  | 'chocolate'

RecipeIngredients
| recipe_id | ingredient_id
|     1     |      2
|     1     |      3
|     2     |      1
|     2     |      2
|     3     |      1
|     3     |      3

Comment construire une requête SQL pour trouver des recettes où ingredients.name = « chocolat » et ingredients.name = « crème »?

Était-ce utile?

La solution

est appelée division relationnelle. Diverses techniques sont discutées ici .

Une alternative est pas encore donné la double NOT EXISTS

SELECT r.id, r.name
FROM Recipes r
WHERE NOT EXISTS (SELECT * FROM Ingredients i
                  WHERE name IN ('chocolate', 'cream')
                  AND NOT EXISTS
                      (SELECT * FROM RecipeIngredients ri
                       WHERE ri.recipe_id = r.id
                       AND ri.ingredient_id = i.id))

Autres conseils

Utilisation:

  SELECT r.name
    FROM RECIPES r
    JOIN RECIPEINGREDIENTS ri ON ri.recipe_id = r.id
    JOIN INGREDIENTS i ON i.id = ri.ingredient_id
                      AND i.name IN ('chocolate', 'cream')
GROUP BY r.name
  HAVING COUNT(DISTINCT i.name) = 2

Le point clé est que le nombre doit être égal au nombre de noms d'ingrédients. Si ce n'est pas un compte distinct, il y a un risque de faux positifs en raison de doublons.

Si vous êtes à la recherche de plusieurs associations, puis la façon la plus simple d'écrire la requête est d'utiliser plusieurs conditions de EXISTS au lieu d'un seul JOIN droit.

SELECT r.id, r.name
FROM Recipes r
WHERE EXISTS
(
    SELECT 1
    FROM RecipeIngredients ri
    INNER JOIN Ingredients i
        ON i.id = ri.ingredient_id
    WHERE ri.recipe_id = r.id
    AND i.name = 'chocolate'
)
AND EXISTS
(
    SELECT 1
    FROM RecipeIngredients ri
    INNER JOIN Ingredients i
        ON i.id = ri.ingredient_id
    WHERE ri.recipe_id = r.id
    AND i.name = 'cream'
)

Si vous êtes sûr que les associations sont uniques (soit une recette unique ne peut avoir une seule instance de chaque ingrédient), alors vous pouvez tricher un peu en utilisant un sous-requête de regroupement avec une fonction de COUNT et peut-être accélérer (performance dépend du SGBD):

SELECT r.id, r.Name
FROM Recipes r
INNER JOIN RecipeIngredients ri
    ON ri.recipe_id = r.id
INNER JOIN Ingredients i
    ON i.id = ri.ingredient_id
WHERE i.name IN ('chocolate', 'cream')
GROUP BY r.id, r.Name
HAVING COUNT(*) = 2

Ou, si une recette peut avoir plusieurs instances du même ingrédient (pas de contrainte de UNIQUE sur la table d'association RecipeIngredients), vous pouvez remplacer la dernière ligne avec:

HAVING COUNT(DISTINCT i.name) = 2
select r.*
from Recipes r
inner join (
    select ri.recipe_id
    from RecipeIngredients ri 
    inner join Ingredients i on ri.ingredient_id = i.id
    where i.name in ('chocolate', 'cream')
    group by ri.recipe_id
    having count(distinct ri.ingredient_id) = 2
) rm on r.id = rm.recipe_id
SELECT DISTINCT r.id, r.name
FROM Recipes r
INNER JOIN RecipeIngredients ri ON
    ri.recipe_id = r.id
INNER JOIN Ingredients i ON
    i.id = ri.ingredient_id
WHERE
    i.name IN ( 'cream', 'chocolate' )

édité après le commentaire, merci! Ceci est le droit chemin alors:

SELECT DISTINCT r.id, r.name
FROM Recipes r
INNER JOIN RecipeIngredients ri ON
    ri.recipe_id = r.id
INNER JOIN Ingredients i ON
    i.id = ri.ingredient_id AND
    i.name = 'cream'
INNER JOIN Ingredients i2 ON
    i2.id = ri.ingredient_id AND
    i2.name = 'chocolate'

d'une manière différente:

Version 2 (procédure stockée) révisée

select   r.name
from   recipes r
where   r.id  = (select  t1.recipe_id
        from  RecipeIngredients t1 inner join
     RecipeIngredients     t2 on t1.recipe_id = t2.recipe_id
     and     t1.ingredient_id = @recipeId1
     and     t2.ingredient_id = @recipeId2)

Edit 2: [Avant que les gens commencent à crier]:)

Il peut être placé en haut de la version 2, qui permettra de requête par nom au lieu de passer dans l'id.

select @recipeId1 = recipe_id from Ingredients where name = @Ingredient1
select @recipeId2 = recipe_id from Ingredients where name = @Ingredient2

Je l'ai testé la version 2, et cela fonctionne. La plupart des utilisateurs où barettes sur la table des ingrédients, dans ce cas était totalement pas nécessaire!

Modifier 3: (résultats d'essai);

Lorsque cette procédure stockée est exécutée ce sont les résultats.

Les résultats sont du format (première recette_id, deuxième recette_id, Résultat)

1,1, Failed
1,2, 'banana cream pie'
1,3, 'chocolate banana surprise'
2,1, 'banana cream pie'
2,2, Failed
2,3, 'chocolate cream pie'
3,1, 'chocolate banana surprise'
3,2, 'chocolate cream pie'
3,3, Failed

Il est clair que cette requête ne gère pas le cas lorsque les deux contraintes sont les mêmes, mais des œuvres pour tous les autres cas.

Modifier 4: (manipulation même cas de contrainte):

Le remplacement de cette ligne:

r.id = (select t1...

à

r.id in (select t1...

fonctionne avec les cas pas réussi à donner:

1,1, 'banana cream pie' and 'chocolate banana surprise'
2,2, 'chocolate cream pie' and 'banana cream pie'
3,3, 'chocolate cream pie' and 'chocolate banana surprise'
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top