21

给定下表:

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

如何构建 SQL 查询来查找成分名称 = 'chocolate' 和成分名称 = 'cream' 的食谱?

4

6 回答 6

15

采用:

  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

这里的关键点是计数必须等于成分名称的数量。如果它不是一个独特的计数,则存在由于重复而导致误报的风险。

于 2010-06-12T17:15:40.957 回答
11

这称为关系划分。这里讨论了多种技术。

尚未给出的另一种选择是双重不存在

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))
于 2010-06-12T22:52:22.353 回答
4

如果您正在搜索多个关联,那么编写查询的最简单方法是使用多个EXISTS条件而不是单个直接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'
)

如果您确定关联是唯一的(即单个配方只能具有每种成分的单个实例),那么您可以使用带有COUNT函数的分组子查询来作弊并可能加快速度(性能将取决于数据库管理系统):

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

或者,如果一个配方可能有多个相同成分的实例(关联表UNIQUE上没有约束RecipeIngredients),您可以将最后一行替换为:

HAVING COUNT(DISTINCT i.name) = 2
于 2010-06-12T17:13:03.033 回答
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
于 2010-06-12T17:14:37.230 回答
1
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' )

编辑以下评论,谢谢!这是正确的方法:

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'
于 2010-06-12T17:16:02.967 回答
1

一种不同的方式:

版本 2(作为存储过程)已修订

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)

编辑2:[在人们开始尖叫之前] :)

这可以放在版本 2 的顶部,这将允许按名称查询而不是传入 id。

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

我已经测试了版本 2,它可以工作。大多数用户在成分表上链接,在这种情况下完全不需要!

编辑3:(测试结果);

运行此存储过程时,这些是结果。

结果的格式为 (First Recipe_id ; Second Recipe_id, Result)

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

显然,此查询不处理两个约束相同的情况,但适用于所有其他情况。

编辑4:(处理相同的约束情况):

替换这一行:

r.id = (select t1...

r.id in (select t1...

与失败的案例一起工作,以提供:

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'
于 2010-06-12T23:18:54.183 回答