SQL EXISTS语句如何工作?


88

我正在尝试学习SQL,并且很难理解EXISTS语句。我碰到了有关“存在”的报价,但不了解以下内容:

使用exists运算符,您的子查询可以返回零,一或多个行,并且条件仅检查子查询是否返回了任何行。如果查看子查询的select子句,您将看到它由单个文字(1)组成;由于包含查询中的条件仅需要知道已返回多少行,因此子查询返回的实际数据无关紧要。

我不明白的是外部查询如何知道子查询正在检查哪一行?例如:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

我知道,如果来自供应商和订单表的ID匹配,则子查询将返回true,并且将输出供应商表中匹配行的所有列。我不明白的是,如果仅返回true或false,则子查询如何传达应打印哪个特定行(假设供应商ID为25的行)。

在我看来,外部查询和子查询之间没有任何关系。

Answers:


99

这样想:

对于“来自”的“每一行” Suppliers,请检查表中是否存在“Order满足”条件的行Suppliers.supplier_id(来自外部查询当前的“行”)= Orders.supplier_id。当您找到第一个匹配的行时,就在此处停止-WHERE EXISTS已满足。

外部查询和子查询之间的魔术链接在于,Supplier_id对于所评估的每一行,该事实都从外部查询传递到子查询。

或者,换句话说,对外部查询的每个表行执行子查询。

子查询不是整体执行并获得'true / false',然后尝试将此'true / false'条件与外部查询进行匹配。


7
谢谢!“这与整体执行子查询并获得'true / false',然后尝试将'true / false'条件与externalquery匹配”不同。是真正为我清除的东西,我一直认为这是子查询的工作方式(很多时候它们都可以这样做),但是您说的很有意义,因为子查询依赖于外部查询,因此必须每行执行一次
Clarence Liu

32

在我看来,外部查询和子查询之间没有任何关系。

您认为EXISTS示例中的WHERE子句在做什么?当SUPPLIERS引用不在EXISTS子句的FROM或JOIN子句中时,您如何得出该结论?

EXISTS评估为TRUE / FALSE,并在条件的第一个匹配项时以TRUE退出-这就是为什么它可以比更快的原因IN。另请注意,EXISTS中的SELECT子句将被忽略-IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

...应该除以零错误,但不会。WHERE子句是EXISTS子句中最重要的部分。

还应注意,JOIN不能直接替代EXISTS,因为如果有多个与父级关联的子级记录,则父级记录将重复。


1
我仍然想念一些东西。如果它在第一个匹配项中退出,那么在o.supplierid = s.supplierid的情况下,输出将如何最终成为所有结果?它不是只输出第一个结果吗?

3
@Dan:EXISTS出口,在第一个匹配项上返回TRUE-因为供应商在ORDERS表中至少存在一次。如果由于在ORDERS中有多个子关​​系而希望查看SUPPLIER数据的重复,则必须使用JOIN。但是大多数人不希望重复,运行GROUP BY / DISTINCT可能会增加查询的开销。 EXISTSSELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...在SQL Server上更有效,最近尚未在Oracle或MySQL上进行测试。
OMG小马

我有一个问题,是对外部查询中选择的每个记录进行匹配。同样,如果从供应商中选择了5行,我们将从订单中提取5次。
Rahul Kadukar '16

24

您可以使用产生相同的结果JOINEXISTSIN,或INTERSECT

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o

1
一个很好的答案,但也要记住,最好不要使用它来避免相关性
FlorianFröhlich13年

1
如果供应商的行数为1000万,订单的行数为1亿,您认为哪个查询运行得更快?为什么?
Teja's

7

如果您的where子句如下所示:

WHERE id in (25,26,27) -- and so on

您可以轻松地理解为什么返回某些行而有些不返回。

当where子句是这样的时:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

这仅意味着:返回在orders表中具有相同ID的现有记录的行。


2

这是一个很好的问题,所以我决定在我的博客上写一篇关于该主题的非常详细的文章

数据库表模型

假设我们的数据库中有以下两个表,它们构成一对多的表关系。

SQL EXISTS表

student表是父student_grade表,它是子表,因为它有一个student_id外键列,该列引用了学生表中的id主键列。

student table包含以下两个记录:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

并且,该student_grade表存储学生收到的成绩:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL存在

假设我们要让所有在数学课上获得10年级的学生。

如果我们只对学生标识符感兴趣,则可以运行如下查询:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

但是,应用程序希望显示a的全名student,而不仅仅是标识符,因此我们也需要student表中的信息。

为了过滤student数学成绩为10的记录,我们可以使用EXISTS SQL运算符,如下所示:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

运行上面的查询时,我们可以看到仅选择了Alice行:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

外部查询选择student我们有兴趣返回到客户端的行列。但是,WHERE子句将EXISTS运算符与关联的内部子查询一起使用。

如果子查询返回至少一条记录,则EXISTS运算符返回true;如果未选择任何行,则返回false。数据库引擎不必完全运行子查询。如果匹配单个记录,则EXISTS运算符将返回true,并选择关联的其他查询行。

内部子查询是相关的,因为student_grade表的student_id列与外部学生表的id列匹配。


真是个好答案。我想我没有理解这个概念,因为我使用了错误的示例。是否EXIST只与相关子查询工作?我在玩只包含1个表的查询,例如SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1)。我知道我写的内容可以通过一个简单的WHERE查询来实现,但我只是用它来理解EXISTS。我得到了所有的行。确实是因为我没有使用相关子查询吗?谢谢。
刘宝文

当您要过滤外部查询的记录时,仅对相关的子查询有意义。在您的情况下,内部查询可以替换为WHERE TRUE
Vlad Mihalcea

谢谢弗拉德。那正是我所想。当我弄乱它的时候,这只是一个奇怪的想法。老实说,我不知道相关子查询的概念。现在,使用内部查询过滤掉外部查询的行变得更加有意义。
刘宝文

0

EXISTS表示子查询至少返回一行,就是这样。在这种情况下,它是一个相关子查询,因为它会将外部表的Supplier_id与内部表的Supplier_id进行检查。该查询实际上说:

选择所有供应商对于每个供应商ID,请查看是否存在该供应商的订单。如果该供应商不在订单表中,请从结果中删除该供应商。返回所有在订单表中具有相应行的供应商

在这种情况下,您可以使用INNER JOIN执行相同的操作。

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

小马的评论是正确的。您需要对该联接进行分组,或者根据需要的数据选择不同的联接。


4
如果多个父记录关联一个子记录,则内部联接将产生与EXISTS不同的结果-它们不相同。
OMG小马

我认为我可能感到困惑,因为我已经读到带有EXISTS的子查询返回true或false;但这不是它返回的唯一内容,对吧?子查询是否还返回所有“在订单表中具有相应行的供应商”?但是,如果是,则EXISTS语句如何返回布尔结果?我在教科书中阅读的所有内容都在说,它只会返回布尔结果,因此我很难将代码的结果与所告诉的结果相协调。

像函数一样读取EXISTS ... EXISTS(resultset)。如果结果集有行,则EXISTS函数将返回true,如果结果集为空,则返回false。基本上就是这样。
David Fells

3
@Dan,请考虑为每个源行在逻辑上独立评估EXISTS()-对于整个查询,它不是单个值。
Arvo

-1

您所描述的是带有相关子查询的所谓查询。

(通常)您应该尝试通过使用联接来编写查询来避免这种情况:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

因为否则,将为外部查询中的每一行执行子查询。


2
这两种解决方案并不相同。如果有多个行orders符合联接条件,则JOIN给出的结果与EXISTS子查询不同。
2011年

1
感谢您的替代解决方案。但是您是否建议,如果在相关子查询和联接之间选择一个选项,我应该选择联接,因为它效率更高?
sunny_dev 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.