unir una tablas tabla relacion registros muchos llenar intermedia inner crear concatenar como sql join where relational-division

sql - una - tabla intermedia uno a muchos



Consulta SQL a través de una tabla intermedia. (6)

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 construyo una consulta SQL para encontrar recetas donde ingredients.name = ''chocolate'' and ingredients.name = ''cream''?


Esto se llama división relacional. Una variedad de técnicas se discuten here .

Una alternativa aún no dada 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))


Si está buscando varias asociaciones, la forma más sencilla de escribir la consulta es utilizar varias condiciones EXISTS lugar de una ÚNICA JOIN .

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 está seguro de que las asociaciones son únicas (es decir, una sola receta puede tener solo una instancia de cada ingrediente), puede hacer trampa un poco utilizando una subconsulta de agrupación con una función COUNT y posiblemente acelerarla (el rendimiento dependerá de el 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 puede tener múltiples instancias del mismo ingrediente (sin restricción UNIQUE en la tabla de asociación de RecipeIngredients ), puede reemplazar la última línea con:

HAVING COUNT(DISTINCT i.name) = 2


Utilizar:

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 se trata de un recuento distinto, existe el riesgo de falsos positivos debido a duplicados.


una forma diferente:

Versión 2 (según 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)

Edición 2: [antes de que la gente empiece a gritar] :)

Esto se puede colocar en la parte superior de la versión 2, lo que permitirá realizar consultas por nombre en lugar de pasar 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 que se vinculaban en la tabla de ingredientes, en este caso no era totalmente necesario.

Edición 3: (resultados de la prueba);

Cuando se ejecuta este procedimiento almacenado estos son los resultados.

Los resultados son del formato (Primera Receta_id; Segunda 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

Claramente, esta consulta no maneja el caso cuando ambas restricciones son iguales, pero funciona para todos los demás casos.

Edición 4: (manejando el mismo caso de restricción):

reemplazando esta linea:

r.id = (select t1...

a

r.id in (select t1...

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


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! Esta es la manera correcta entonces:

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


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