【问题标题】:How do SQL EXISTS statements work?SQL EXISTS 语句如何工作?
【发布时间】:2011-08-16 08:31:46
【问题描述】:

我正在尝试学习 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 的行)。

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

【问题讨论】:

    标签: sql


    【解决方案1】:

    这样想:

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

    外部查询和子查询之间的神奇联系在于Supplier_id 从外部查询传递到每个评估行的子查询。

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

    这不像是子查询在整体上执行并获得'真/假'然后尝试将这个'真/假'条件与外部查询匹配。

    【讨论】:

    • 谢谢! “它不像子查询在整体上执行并获得'真/假',然后尝试将这个'真/假'条件与外部查询匹配。”是真正为我清除它的原因,我一直认为这就是子查询的工作方式(并且很多次他们这样做),但是您所说的有道理,因为子查询依赖于外部查询,因此必须每行执行一次
    【解决方案2】:

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

    您认为 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 的直接替代品,因为如果有多个子记录与父记录关联,则会出现重复的父记录。

    【讨论】:

    • 我仍然缺少一些东西。如果它在第一次匹配时退出,输出如何最终成为 o.supplierid = s.supplierid 的所有结果?它不会只是输出第一个结果吗?
    • @Dan:EXISTS 退出,在第一次匹配时返回 TRUE - 因为供应商在 ORDERS 表中至少存在一次。如果由于 ORDERS 中有多个子关​​系而想查看 SUPPLIER 数据的重复,则必须使用 JOIN。但大多数人不希望这种重复,并且运行 GROUP BY/DISTINCT 可能会增加查询的开销。 EXISTS 在 SQL Server 上比 SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ... 效率更高,最近没有在 Oracle 或 MySQL 上测试过。
    • 我有一个问题,是否对外部查询中选择的每条记录进行匹配。如果从供应商中选择了 5 行,我们是否会从 Orders 中获取 5 次。
    【解决方案3】:

    您可以使用JOINEXISTSININTERSECT 产生相同的结果:

    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
    

    【讨论】:

    • 很好的答案,但也要注意最好不要使用存在来避免相关性
    • 如果供应商有 10M 行而订单有 100M 行,您认为哪个查询会运行得更快,为什么?
    【解决方案4】:

    如果你有一个看起来像这样的 where 子句:

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

    您可以很容易地理解为什么有些行会返回而有些则不会。

    当where子句是这样的时候:

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

    它只是意味着:返回在 order 表中具有相同 ID 的现有记录的行。

    【讨论】:

      【解决方案5】:

      数据库表模型

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

      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
      

      但是,应用程序有兴趣显示 student 的全名,而不仅仅是标识符,因此我们还需要来自 student 表的信息。

      为了过滤数学成绩为 10 的student 记录,我们可以使用 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 列与外部 student 表的 id 列匹配。

      【讨论】:

      • 多么棒的答案。我想我没有得到这个概念,因为我使用了一个错误的例子。 EXIST 是否仅适用于相关子查询?我在玩只包含 1 个表的查询,比如SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1)。我知道我写的内容可以通过一个简单的 WHERE 查询来实现,但我只是用它来理解 EXISTS。我得到了所有的行。真的是因为我没有使用相关子查询吗?谢谢。
      • 它只对相关子查询有意义,因为您要过滤外部查询的记录。在您的情况下,内部查询可以替换为 WHERE TRUE
      • 谢谢弗拉德。那正是我所想。这只是我在搞砸它时出现的一个奇怪的想法。老实说,我不知道相关子查询的概念。现在用内部查询过滤掉外部查询的行更有意义。
      【解决方案6】:

      EXISTS 表示子查询至少返回一行,就是这样。在这种情况下,它是一个相关子查询,因为它检查外部表的供应商 ID 和内部表的供应商 ID。这个查询实际上说:

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

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

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

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

      【讨论】:

      • 如果多个子记录与父记录关联,则内部联接将产生与 EXISTS 不同的结果——它们不相同。
      • 我想我的困惑可能是我读过带有 EXISTS 的子查询返回 true 或 false;但这不可能是它返回的唯一东西,对吧?子查询是否还返回所有“在订单表中有相应行的供应商”?但如果是,EXISTS 语句如何返回布尔结果?我在教科书中读到的所有内容都在说它只返回一个布尔结果,所以我很难将代码的结果与我被告知它返回的结果相协调。
      • 像函数一样读取 EXISTS... EXISTS(resultset)。如果结果集有行,则 EXISTS 函数将返回 true,如果为空,则返回 false。基本上就是这样。
      • @Dan,考虑到 EXISTS() 对 每个 源行独立进行逻辑评估 - 它不是整个查询的单个值。
      【解决方案7】:

      您所描述的是带有correlated subquery 的所谓查询。

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

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

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

      【讨论】:

      • 这两种解决方案并不等同。如果orders 中有不止一行与连接条件匹配,则 JOIN 给出的结果与 EXISTS 子查询不同。
      • 感谢您提供替代解决方案。但是您是否建议如果在相关子查询和联接之间给出一个选项,我应该使用联接,因为它更有效?
      猜你喜欢
      • 1970-01-01
      • 2013-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-28
      • 1970-01-01
      • 2016-09-16
      • 1970-01-01
      相关资源
      最近更新 更多