有一个时间的噩梦了解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分的女学生。
答案 0 :(得分:13)
当你掌握它时,这很简单:
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
。使用EXISTS
和NOT 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 EXISTS
列都不相同时,查询的(看似等效的)NOT 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 IN
转换为NOT EXISTS
并保持与null相关(令人不快)行为的方法。
答案 1 :(得分: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的一般理解。有优点和缺点对两种方法都有所帮助。