查找没有满足特定条件的多对多子项的行

时间:2010-11-18 21:57:33

标签: mysql optimization join subquery

这是我正在尝试做的通用版本:

recipes包含字段idname。表格ingredients包含字段idnamesweetness,描述了该成分在1-10的范围内的甜度。食谱中含有许多成分和成分,因此两者在ingredients_recipes表中相关,包含字段ingredient_idrecipe_id

很容易找到含有甜度为10的成分的食谱。

SELECT DISTINCT recipes.* FROM recipes
INNER JOIN recipes_ingredients ri ON ri.recipe_id = recipes.id
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness = 10

然而,我在否定该查询以找到含有 no 成分和甜味10的食谱时遇到了麻烦。我的第一个想法是:

SELECT DISTINCT recipes.* FROM recipes
INNER JOIN recipes_ingredients ri ON ri.recipe_id = recipes.id
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness != 10

但是,它会找到包含任何非甜味-10 成分的食谱。

我的下一次尝试是以下,似乎有效:

SELECT * FROM recipes WHERE
(
  SELECT count(*) FROM ingredients INNER JOIN recipes_ingredients ri ON
  ri.ingredient_id = ingredients.id WHERE ingredients.sweetness = 10 AND
  ri.recipe_id = recipes.id
) = 0

但是,我的一般经验是,与等效的,精心设计的JOIN相比,从属子查询的运行速度很慢。我一直在玩加入,分组等等,但是我不能完全围绕它,尤其是因为,虽然看起来像LEFT JOINIS NULL是合适的工具,但已经有两个联接讨厌。伟大的SQL向导,我可以运行什么查询以获得最佳结果?谢谢!

3 个答案:

答案 0 :(得分:1)

试试这个:

SELECT DISTINCT recipes.* 
FROM recipes r LEFT JOIN
(SELECT ri.recipe_id
FROM recipes_ingredients ri 
INNER JOIN ingredients ON ingredients.id = ri.ingredient_id
WHERE ingredients.sweetness = 10) i on i.recipe_id=r.recipe_id
WHERE i.recipe_id is null

答案 1 :(得分:1)

尝试:

select
  r.*
from
  recipes r
where
  not exists (
    select
      1
    from
      recipe_ingredients ri
      join ingredients i on ri.ingredient_id = ri.ingredient_id
    where
      ri.recipie_id = r.recipe_id
      and i.sweetness = 10
  )

它仍然是一个相关的子查询,但existsnot exists进行了一些优化,使它们的性能优于原始查询。

对于直接联接解决方案,此应该工作:

select distinct
  r.*
from
  recipes r
  join recipe_ingredients ri on ri.recipe_id = r.recipe_id
  left join ingredents i on i.ingredient_id = ri.ingredient_id and i.sweetness = 10
where
  i.ingredient_id is null

取决于索引,not exists解决方案可能会更快,因为not exists在确定是否有任何行满足给定条件时立即返回,而不会查看超出必要的表格。例如,如果它找到单行甜度10,它将停止查看该表并返回false。

答案 2 :(得分:0)

我在这里找到了给我的答案(我已经投票了),并且从他们的灵感中,我们提出了一个看起来似乎以惊人的出色表现完成工作的查询:

SELECT r.* FROM recipes r
LEFT JOIN recipes_ingredients ri ON ri.parent_id = r.id
LEFT JOIN ingredients i ON i.id = ri.ingredient_id AND i.sweetness = 10
GROUP BY r.id HAVING MAX(i.id) IS NULL

与内部条件的连接(受@Donnie启发)带出配方成分组合,如果成分不是甜味,则为NULL行。然后按配方ID分组,并选择“最大”成分ID。 (当且仅当没有要选择的实际ID时,MAX函数将返回null,即,绝对没有与此配方相关联的非甜味-10项目可供选择。)如果该“最大”成分ID为null,然后MAX函数没有甜度-10项,因此选择行HAVING为空MAX(i.id)

我在查询cacher已禁用的情况下多次运行查询的NOT EXISTS版本和上述版本的查询。对于大约400个配方,NOT EXISTS查询将始终需要大约1.0秒才能完成,而此查询的运行时通常大约为0.1秒。对于大约5000个食谱,NOT EXISTS查询大约需要30秒,而上述查询通常需要0.1秒,并且几乎总是低于1.0。

值得注意的是,检查每个上的EXPLAINs,这里列出的查询几乎完全可以运行我给这些表的索引,这可能解释了为什么它能够进行各种加入和分组而不打击眼睛。另一方面,NOT EXISTS查询必须执行从属子查询。如果这些索引没有到位,那么两者可能会更平等地执行,但是如果有机会使用原始联接,那么查询优化器相当强大,看起来就好了。

故事的道德:结构良好的JOIN非常强大:)非常感谢!