Pregunta

Dadas las siguientes tablas:

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

¿Cómo construir una consulta SQL para encontrar recetas en las que ingredients.name = 'chocolate' y ingredients.name = 'crema'?

¿Fue útil?

Solución

Esto se llama división relacional. se discuten una variedad de técnicas aquí .

Una alternativa aún no determinado es el doble NO EXISTE

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))

Otros consejos

Uso:

  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

El punto clave aquí es que el conteo debe ser igual al número de nombres de ingredientes. Si no es un recuento distinta, hay un riesgo de falsos positivos debido a los duplicados.

Si estás en busca de múltiples asociaciones continuación, la forma más sencilla de escribir la consulta es el uso de múltiples condiciones EXISTS en lugar de una sola JOIN recta.

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 sabe con certeza que las asociaciones son únicos (es decir, una única receta sólo puede tener una única instancia de cada ingrediente), entonces se puede engañar un poco utilizando una subconsulta agrupación con una función COUNT y posiblemente acelerarlo (rendimiento dependerá de la DBMS):

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

O, si una receta podría tener varias instancias del mismo ingrediente (sin limitación UNIQUE en la tabla de asociación RecipeIngredients), puede reemplazar la última línea con:

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' )

Editado siguiente comentario, gracias! Este es el camino correcto a continuación:

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'

una manera diferente:

Version 2 (procedimiento almacenado) revisada

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)

Editar 2: [Ante la gente empieza a gritar]:)

Esto se puede colocar en la parte superior de la versión 2, lo que permitirá a la pregunta por su nombre en lugar de pasar en el id.

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

He probado la versión 2, y funciona. La mayoría de los usuarios en los puentes sobre la mesa ingrediente, en este caso totalmente de que no era necesario!

Editar 3: (resultados de la prueba);

Cuando se ejecuta este procedimiento almacenado estos son los resultados.

Los resultados son del formato (Primera receta_id, en segundo lugar receta_id, Resultado)

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

Es evidente que esta consulta no maneja caso cuando ambas restricciones son las mismas, pero funciona para todos los demás casos.

Editar 4: (manipulación mismo caso restricción):

sustitución de esta línea:

r.id = (select t1...

a

r.id in (select t1...

trabajos con los casos fallidos para dar:

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'
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top