使用WHERE NOT EXISTS的SQL子查询

时间:2012-07-03 00:14:13

标签: mysql subquery

我想选择你的角色在过去24小时内没有挑战过的所有角色。

 SELECT * FROM challenges
 WHERE userCharID = 642 AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)

这将返回几行,其中包含您的角色在过去一天发起的挑战

SELECT characterID FROM CHARACTERS 
WHERE NOT EXISTS (SELECT * FROM challenges
                   WHERE userCharID = '610'
                     AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY))

我是否使用WHERE NOT EXISTS错误?

3 个答案:

答案 0 :(得分:3)

  

我是否使用WHERE NOT EXISTS错误?

是。你想使用NOT IN而不是NOT EXISTS。如果使用NOT EXISTS并且非存在子查询返回任何行,则条件将为false,并且主查询不会返回任何数据。如果没有返回任何行,则条件将为true,并且主查询将返回所有行(因为,在此示例中,主查询中没有其他条件)。通常,NOT EXISTS中的子查询是相关的子查询,因此必须为每一行评估子查询。在这里,您没有相关的子查询(这对性能有利)。但是您的查询意味着“返回有关所有角色的信息,除非在指定用户的最后一天内有一些角色受到挑战。”

在此分析中,我已经悄悄地更改了SQL,以便始终将userCharID与字符串进行比较,并特别使用值'642'进行比较。

  

选择过去24小时内角色[ ]挑战的所有角色:

SELECT *
  FROM Challenges
 WHERE userCharID = '642'
   AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
     

这会返回几行,其中包含您的角色在过去一天发起的挑战。

因此,要找到您未挑战的所有人,您需要选择除了您所挑战的列表中的所有用户之外的所有用户,这些用户将转换为:

SELECT characterID
  FROM Characters 
 WHERE userCharID NOT IN
       (SELECT userCharID
          FROM Challenges
         WHERE userCharID = '642'
           AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
       )

这应该会为您提供在过去24小时内未挑战过的(可能相当大的)角色列表。

答案 1 :(得分:2)

子查询上下文中的

WHERE NOT EXISTS根据结果返回TRUE或FALSE。

  

如果子查询完全返回任何行,则EXISTS子查询为TRUE,NOT EXISTS子查询为FALSE。

在你的情况下,这意味着如果

(SELECT * FROM challenges
WHERE userCharID = '610' AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY))

然后返回所有行

您的查询将被评估为

SELECT characterID FROM CHARACTERS WHERE FALSE; 

这显然不是你想要的。

您可以改为使用IN运算符:

SELECT characterID FROM CHARACTERS 
WHERE characterID NOT IN (SELECT characterID FROM challenges
WHERE userCharID = '610' AND chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY))

第二个characterID(子查询中的那个)需要是与CHARACTERS表中的characterID相对应的字段,这可能是userCharID,但我怀疑它,给你的where子句。没有架构,我无法确定。

您可以使用其他选项直接选择from the subquery,或者在某些情况下通过joins获取数据。

答案 2 :(得分:0)

您的NOT EXIST查询非常接近。您缺少的是子查询与characterID上的外部查询之间的关联。

我刚刚在外部查询的表中添加了别名c,在子查询中的表中添加了别名d,并在子查询的WHERE子句中添加了一个谓词

SELECT characterID FROM CHARACTERS c 
WHERE NOT EXISTS (SELECT * FROM challenges d
                   WHERE d.userCharID = '610'
                     AND d.chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
                     AND d.characterID = c.characterID)    

这里的“技巧”是相关匹配d.characterID(从子查询中的表)到c.characterID(来自外部查询中的表)。

因此,查询正在检查该外表中的每个字符,无论我们的用户是否在过去24小时内遇到 用户的挑战。因此,此查询将返回您指定的结果集。

但是......如果你有一个相对较大的角色集,而且一个相对较小的一组被挑战,这不可能是返回结果集的最快查询。


获取结果集的另一种方法是使用带有IS NULL谓词的LEFT JOIN(我们将其称为“反连接”。)如果此查询:

SELECT d.characterID
  FROM challenges d
 WHERE d.userCharID = 642
   AND d.chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
 GROUP BY d.characterID

返回所有已被挑战的characterID的列表,这是您要从所有字符集中排除的字符集,然后您可以将该查询用作内联视图,如下所示:

SELECT n.characterID
  FROM characters n
  LEFT
  JOIN (
         SELECT d.characterID
           FROM challenges d
          WHERE d.userCharID = 642
            AND d.chalTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
          GROUP BY d.characterID
       ) c
    ON c.characterID = n.characterID
 WHERE c.characterID IS NULL

这里我们得到所有字符的列表(n),并将它们与已经被挑战的字符列表匹配(子查询别名为c)。我们使用LEFT JOIN操作,因为我们需要来自字符表的所有行,无论是否找到匹配项。

然后WHERE子句会抛出我们找到匹配项的所有行,所以剩下的就是没有被质疑的字符集。


在我使用大集测试时,这通常会优于NOT EXISTSNOT IN(当适当的索引可用时)。但有时我发现NOT IN更快,有时NOT EXISTS更快。

我觉得将所有三种方法“放在口袋里”都很好,并使用最合适的方法。我通常从反连接模式开始(这是我以前写的),然后测试NOT EXISTSNOT IN来比较性能。