6

了解 NOT EXISTS 的用法是一场噩梦,主要是如何在下面转换我的 NOT IN 解决方案,以便我能够真正了解我是如何获得结果的。在 askTom、oracle 论坛和 stackoverflow 上有几篇文章,但找不到任何可以清楚地帮助理解这个问题的文章。如果我因愚蠢的搜索而错过了它,我深表歉意。

SELECT s.S_Fname, s.S_Lname
FROM STUDENT s
WHERE s.S_Sex = 'F'
AND S.S_Id NOT IN(SELECT e.S_Id
        FROM ENROLLMENT e
        WHERE e.Mark < 70);

内容方面的一点帮助,试图找到在他们注册的任何课程中从未获得低于 70 分的女学生。

4

2 回答 2

16

这很简单,当你掌握了它的窍门时:

SELECT s.S_Fname, s.S_Lname
FROM STUDENT s
WHERE s.S_Sex = 'F'
AND S.S_Id NOT IN(SELECT e.S_Id           -- take this line
        FROM ENROLLMENT e
        WHERE e.Mark < 70);

该行基本上与来自子查询S.S_Id的所有值进行比较。e.S_Id

现在将其更改为NOT EXISTS并在子查询中放置一个相等检查S.S_Id = e.S_Id

SELECT s.S_Fname, s.S_Lname
FROM STUDENT s
WHERE s.S_Sex = 'F'
AND NOT EXISTS (SELECT e.S_Id          
        FROM ENROLLMENT e
        WHERE (e.Mark < 70)       -- if this is complex, you'll need parentheses
        AND S.S_Id = e.S_Id);

微小的可能变化是意识到(SELECT e.S_Id ...并不真正需要e.S_Id. 子查询EXISTSNOT EXISTS检查是否有行返回,列值无关紧要。您可以在SELECT *那里放置一个常量(SELECT 1很常见)SELECT NULL甚至SELECT 1/0(是的,这会起作用!):

SELECT s.S_Fname, s.S_Lname
FROM STUDENT s
WHERE s.S_Sex = 'F'
AND NOT EXISTS (SELECT 1
        FROM ENROLLMENT e
        WHERE e.Mark < 70  
        AND S.S_Id = e.S_Id);

另一个主要考虑因素是,当您以这种方式进行转换时,只有当两列都不可为空时,查询的(看似等效的)NOT EXISTSNOT IN写入才真正等效。S_Id如果该e.S_Id列可以为空,则NOT IN可能导致整个查询根本不返回任何行(因为当其中一个x NOT IN (a, b, c, ...)x<>a AND x<>b AND ...时,该条件不能为真a,b,c...NULL

出于类似的原因,如果 可以为空,您将得到不同的结果s.S_Id(在这种情况下不太可能,因为它可能是主键,但在其他情况下很重要。)

因此,使用 几乎总是更好NOT EXISTS,因为即使任一列可以为空(S.S_Id = e.S_Id检查将丢弃之前为 null 的行),它的行为也会有所不同,并且通常这种行为是想要的行为。问题中有很多细节:NOT IN vs NOT EXISTS,@Martin Smith 的回答。您还将找到将其转换NOT INNOT EXISTS并保持与 null 相关(不愉快)行为的方法。

于 2013-05-23T06:50:40.647 回答
4

不完全符合您的要求,但这里有一种方法可以做到这一点NOT EXISTS- 创建“70 岁以下的成就者”作为派生表,LEFT OUTER JOIN并检查这样的空值......

SELECT s.S_Fname, s.S_Lname 
FROM STUDENT s
LEFT JOIN
(
  SELECT S_Id 
  FROM ENROLLMENT
  WHERE Mark < 70
) e ON e.S_Id = s.S_Id
WHERE e.S_Id IS NULL 
AND s.S_Sex = 'F';

也可能有助于您对 SQL 的一般理解。两种方法各有利弊。

Click here to go to SQL Fiddle

于 2013-05-23T07:01:06.953 回答