【问题标题】:SQL query through an intermediate table通过中间表进行 SQL 查询
【发布时间】:2011-03-03 01:10:23
【问题描述】:

给定以下表格:

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' 的食谱?

【问题讨论】:

  • fyi... recipeIngredients 称为 MappingTable...
  • @Garis Suero:有 很多 个同义词 - 外部参照、查找、映射、链接 - 为提供多对多关系的表定义了标准名称。

标签: sql join where relational-division


【解决方案1】:

用途:

  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

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

【讨论】:

  • 第一个选项看起来像我想要的。我使用 PostgreSQL 8.3.5 和 SQLite3 毫无乐趣地尝试了它。我只尝试了一种成分,对于每种树成分,反过来,我得到了 2 行,正如我所期望的那样。但是,当我尝试使用 2 种成分时,我得到 0 行。
  • 这就是你没有指定你测试的数据库的结果。 SQL 中的 S 并不意味着“标准化”;除了基本的 SELECT 之外,几乎不可能 100% 移植到所有供应商。
  • 这适用于哪个数据库?也许我可以比较语法,看看如何将它应用到 PostgreSQL。你的解决方案风格比 NOT EXISTS 更容易让我理解
  • @Bryan - RE:更容易理解。双重否定确实需要一些时间来适应。用英语重新表述它是说Select those recipes where there isn't an ingredient in the list (chocolate, cream) that isn't in the recipe.
  • @Martin Smith:你说得对,我在 SQL Server 2008 Express 上确认了 - 这是因为内部连接。
【解决方案2】:

这称为关系除法。讨论了各种技术here

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

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

【讨论】:

  • 马丁,感谢您的参考和有效的答案!最后一行有个小错误,应该是“AND ri.ingredient_id = i.id))”
  • @Bryan,干杯。为了完整起见,我已将其固定在我的答案中。
【解决方案3】:

另一种方式:

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

【讨论】:

  • 您似乎假设只有一个 recipe_id 可以匹配,并且没有什么限制它仅限于巧克力和奶油
  • 试用版本 2(基于与第一个版本相同的想法)。我已经对其进行了测试,它确实可以正常工作。
  • 假设只有一个结果,您仍在使用`r.id =`。如果您按成分名称进行匹配,当然需要成分,但我想这可能来自列表并且 Id 将是已知的。
  • 刚刚再次编辑它,因为我知道人们会开始尖叫! :)
  • @Darknight 您是否使用包含巧克力和奶油的 2 种不同食谱的 RecipeIngredients 对此进行了测试?
【解决方案4】:
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'

【讨论】:

  • 这也将返回使用奶油不加巧克力或巧克力不加奶油的食谱。
【解决方案5】:

如果您要搜索多个关联,那么编写查询的最简单方法是使用多个 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 函数的分组子查询来作弊,并可能加快速度(性能取决于 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

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

HAVING COUNT(DISTINCT i.name) = 2

【讨论】:

  • @OMG Ponies:第二段中提到了这个警告。但我想添加替代方案不会有什么坏处(它会运行得更慢)。
【解决方案6】:
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

【讨论】:

    猜你喜欢
    • 2014-05-12
    • 2018-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多