【问题标题】:SQL query INNER JOIN only if all existsSQL 查询 INNER JOIN 仅当全部存在时
【发布时间】:2018-01-08 06:01:37
【问题描述】:

我有两张表,一张包含不同的食谱,另一张包含 recipes_ingredients(食谱所需的所有成分)。


recipes
-----
id_recipe
name
description
etc...

recipe_ingredients
-----
id_recipe_ingredient
id_recipe
id_ingredient

假设我只选择了一种成分(例如鸡蛋)。

SELECT * 
FROM recipes r 
  INNER JOIN recipes_ingredients ri ON r.id_recipe = ri.id_recipe 
WHERE ri.id_ingredient = 1 

这将返回所有包含鸡蛋的食谱。这是合乎逻辑的,但我想创建一个查询,返回所有 ONLY 包含鸡蛋(例如煮鸡蛋)的食谱。不是一些包含鸡蛋的食谱。


如果我同时选择鸡蛋和牛奶并创建相同的查询:

SELECT * 
FROM recipes r 
  INNER JOIN recipes_ingredients ON r.id_recipe = ri.id_recipe 
WHERE ri.id_ingredient = 1 
  OR ri.id_ingredient = 2

我会找到煎蛋卷,但我也会找到所有其他包含鸡蛋和牛奶的食谱。


我要选择的是:

  • 只包含鸡蛋的食谱

  • 只包含鸡蛋和牛奶的食谱

我不想选择包含鸡蛋、牛奶、面粉和糖的蛋糕。


感谢您提供任何可能使我朝着正确方向前进的答案!

【问题讨论】:

  • WHERE ri.id_ingredient = 1 AND ri.id_ingredient = 2 永远不会发生.....你会想要 WHERE ri.id_ingredient = 1 OR ri.id_ingredient = 2;然后检查您是否有 2 个不同的行
  • 当然。我的错,我现在改为 OR。但它并没有真正回答我的问题。如果我想搜索所有包含鸡蛋和牛奶的食谱,我只想返回只包含鸡蛋和牛奶的食谱,例如煎蛋。
  • 您可以在 where 子句之后使用 NOT EXISTS sql 子句来检查任何其他配方成分。另外,您可以检查 APPLY 运算符
  • 似乎是精确关系划分的用例。推荐阅读red-gate.com/simple-talk/sql/t-sql-programming/…

标签: mysql sql database select


【解决方案1】:

计算成分的数量和匹配成分的数量,并检查它们是否匹配。例如,要检索同时包含成分 1 和 2 且仅包含这两种成分的所有食谱:

SELECT id_recipe
FROM recipe_ingredients
GROUP BY id_recipe
HAVING COUNT(*) = 2
  AND COUNT(CASE WHEN id_ingredient IN (1, 2) THEN 1 END) = COUNT(*);

您还可以使用GROUP_CONCAT 将成分 ID 收集到一个字符串中并进行字符串比较:

SELECT id_recipe
FROM recipe_ingredients
GROUP BY id_recipe
HAVING GROUP_CONCAT(id_ingredient ORDER BY id_ingredient) = '1,2';

在前面的查询中,为了便于阅读,我省略了对配方的完整检索。为了完整起见,以下查询将检索完整的配方:

SELECT *
FROM recipes JOIN recipe_ingredients USING (id_recipe)
WHERE id_recipe in (
    SELECT id_recipe
    FROM recipe_ingredients
    GROUP BY id_recipe
    HAVING COUNT(*) = 2
      AND COUNT(CASE WHEN id_ingredient IN (1, 2) THEN 1 END) = COUNT(*)
);

虽然这些查询简短易读,但如果您有大量食谱和/或配料,则可能会遇到性能问题。如果这样做,您可能希望在 id_ingredientid_recipe 上添加索引并尝试将查询重写为

SELECT *
FROM recipes JOIN recipe_ingredients USING (id_recipe)
WHERE id_recipe in (
    SELECT i1.id_recipe
    FROM recipe_ingredients i1
      JOIN recipe_ingredients i2
        ON (i1.id_recipe = i2.id_recipe AND i2.id_ingredient = 2)
      LEFT JOIN recipe_ingredients ni
        ON (i1.id_recipe = ni.id_recipe and ni.id_ingredient NOT IN (1, 2))
    WHERE i1.id_ingredient = 1 AND ni.id_ingredient IS NULL
);

但是,我发现此版本更麻烦且更难维护,因此建议在需要性能之前避免使用它。

【讨论】:

  • 我喜欢这个;精炼到位。您也可以通过将内部连接加入到他的食谱中来增强它..
  • 这看起来对我来说,我要去看看。我可能会遇到性能问题,因为将来会有几千种食谱和许多不同的成分。我将保存最后一个查询以备将来使用!非常感谢。
  • 我刚试了一下,效果很好。我会将此标记为解决方案!
  • 正如我之前提到的,这个按预期工作。但我现在与另一部分斗争。 recipes_ingredients 表还包含成分的数量(称为“type_amount”)。如何将此添加到我的选择查询中?例如,如果我有 3 个鸡蛋和 300 个(基本单位,在本例中为 ML)牛奶,我只想在我的 WHERE 子句匹配 id_ingredient 和 base_units >= to type_amount 时选择该配方。
  • 如果需要匹配多个属性,可以将 IN 表达式重写为一系列谓词 OR'ed 在一起:COUNT(CASE WHEN (id_ingredient = 1 AND type_amount >= 3) OR (id_ingredient = 2 AND type_amount >= 300) THEN 1 END)
【解决方案2】:

用 EGG 和 MILK 的那些只有两种成分,UNION 到那些只有 EGG 和一种成分的........

SELECT r.Id, COUNT(ri.Id) FROM recipes r                            --GET THE RECIPE ID AND COUNT OF INGREDIENTS
INNER JOIN recipes_ingredients ri ON r.id_recipe = ri.id_recipe     --JOIN RECIPE INGREDIENTS
WHERE 
ri.id_ingredient IN (1,2)                                           --EGGS OR MILK
AND r.id NOT IN (SELECT ri2.id FROM recipes_ingredients ri2 WHERE ri2.id NOT IN (1,2)) --AND JUST EGGSD OR MILK
HAVING COUNT(ri.Id) = 2                                             --AND THERE ARE 2 INGREDIENTS
UNION ALL
SELECT r.Id, COUNT(ri.Id) FROM recipes r                            --GET THE RECIPE ID AND COUNT OF INGREDIENTS
INNER JOIN recipes_ingredients ri ON r.id_recipe = ri.id_recipe     --JOIN RECIPE INGREDIENTS
WHERE 
ri.id_ingredient = 1                                                --EGGS
AND r.id NOT IN (SELECT ri2.id FROM recipes_ingredients ri2 WHERE ri2.id !=1)  --AND JUST EGGS
HAVING COUNT(ri.Id) = 1     

【讨论】:

  • COUNT 将只包含符合WHERE 条件的成分,因此您的查询不会过滤掉包含其他成分的食谱。
【解决方案3】:

查询各个成分,并通过将查询中的成分数量与食谱的成分数量相匹配来确保不再有。

编辑

需要的成分一一匹配,以确保所有这些成分都存在于相关食谱中。如果它们都存在并且成分的数量匹配,则不会有更多或更少的成分。

select * 
from recipe_table rt
where rt.id_recipe in (select rig.id_recipe
                    from recipe_ingredient_table rig
                    where rig.id_recipe = rt.id_recipe
                      and <ing_id_1> in (select rig2.id_ingredient from recipe_ingredient_table rig2 where rig2.id_recipe = rig.id_recipe)
                      and <ing_id_2> in (select rig2.id_ingredient from recipe_ingredient_table rig2 where rig2.id_recipe = rig.id_recipe)
                      and ...
                      and (select count(*) 
                           from recipe_ingredient_table
                           where id_recipe = rig.id_recipe) = <number of ingredients>
          )

【讨论】:

  • id_ingredient = &lt;ing_id&gt; 仅适用于单一成分。您需要重写以使用 id_ingredient IN (...) 或使用多个子查询,每种成分一个。
  • 您修改后的查询将无法正常工作。它将返回具有指定数量的成分和至少一种目标成分的食谱。例如,如果有一个包含鸡蛋和面包作为成分的食谱,即使它不应该返回它也会被返回,因为 in 子句匹配(它有鸡蛋)并且count 谓词匹配(它有两种成分)。
  • @markusk 在子句中替换为根据配方的成分逐一检查成分。
  • 现在查询可以正常工作了,只是您需要在第 11 行将 recipe_table.id_recipe 替换为 rt.id_recipe,因为您已经为表设置了别名。请注意,您可以通过将所有子查询提升一级并放弃 where rt.id_recipe in ... 来简化查询。
【解决方案4】:
-- 1 egg only
SELECT *
FROM recipes AS r
WHERE EXISTS (
    SELECT ri.id_recipe, count(*)
    FROM recipes_ingredients AS ri
    WHERE ri.id_recipe = id_recipe 
    AND ri.id_ingredient = 1
    GROUP BY ri.id_recipe
    HAVING COUNT(*) = 1
)

-- 1 egg only OR 2 milk only OR egg and milk
SELECT *
FROM recipes AS r
WHERE EXISTS (
    SELECT ri.id_recipe, count(*)
    FROM recipes_ingredients AS ri
    WHERE ri.id_recipe = id_recipe 
    AND ri.id_ingredient IN (1, 2)
    GROUP BY ri.id_recipe
    HAVING COUNT(*) < 3
)

-- both egg and milk only
-- change above SQL: HAVING COUNT(*) = 2

【讨论】:

  • COUNT 将仅包含与 WHERE 条件匹配的成分,因此您的 EXISTS 子查询不会过滤掉包含其他成分的食谱。
猜你喜欢
  • 1970-01-01
  • 2015-01-10
  • 2010-12-19
  • 2012-11-04
  • 2014-02-24
  • 2017-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多