【问题标题】:Number of times a nested query is executed执行嵌套查询的次数
【发布时间】:2013-09-18 22:47:29
【问题描述】:

这个嵌套的子查询会被执行多少次?

SELECT CID, CNAME 
  FROM CUSTOMER
 WHERE EXISTS ( SELECT CID 
                  FROM RENTALS 
                 WHERE CUSTOMER.CID = RENTALS.CID 
                   AND PICKUP = 'CARY' )

这是一个理论问题,即在我的书中找到。提供的答案是 6,但我不明白为什么会这样。


好的,我认为这本书本身存在一些问题。我会通读这本书,也许稍后再问这个问题。

【问题讨论】:

  • SQL 只是 结构化查询语言 - 许多数据库系统使用的语言,但不是数据库产品...很多事情都是特定于供应商的 - 所以我们真的需要知道您正在使用什么数据库系统(以及哪个版本)(请相应地更新标签)......
  • @marc_s 你好,马克,这是一个理论问题,即在我的书中找到。他们给出的答案是 6。请解释一下。
  • 我试图说明:这很可能取决于哪个实际数据库正在执行此代码,并且它绝对取决于表中存在什么 data涉及。仅仅从这段代码sn-p,这个问题是无法回答的。
  • 即使我们假设最简单的实现,我们也需要知道 CUSTOMER 表中有多少行才能得出一个特定的数字,例如6
  • 对于CUSTOMER 中的每一行,subqery 都可以工作。

标签: sql database-agnostic


【解决方案1】:

这个问题没有正确的理论答案。智能查询优化器可以将查询转换为 JOIN:

SELECT CID, CNAME
FROM Customer
LEFT JOIN (SELECT DISTINCT CID FROM Rentals WHERE PICKUP = 'CARY') as Rentals
ON Customer.CID = Rentals.CID
WHERE Rentals.CID IS NOT NULL

现在子查询只执行一次。

【讨论】:

    【解决方案2】:

    正如其他人指出的那样,这个相关的子查询可以重写为连接,但这并不完全是完整的故事,因为未转换的 EXISTS 的执行计划无论如何都会看起来很像连接。所以这实际上不是语法问题,而是查询优化问题。

    EXISTS 实际上只是“加入该数据集,但即使有 1,000,000 个匹配项也只加入其中的一行”的语法简写,或者也称为semijoin

    因此,EXISTS 谓词针对相关或不相关子查询所需的半连接可以通过多种方式实现,这在很大程度上取决于两个表中的数字或记录。

    如果您想象 CUSTOMER 估计只有一行,而优化器估计 RENTALS 中有数千行 PICKUP = 'CARY',那么优化器很可能会从 CUSTOMER 读取行TABLE 并针对 RENTALS 表执行一次查找。

    如果估计有 100 万个 CUSTOMERS 并且 RENTALS 表中只有一行,那么该执行计划将是疯狂的 - 优化器可以通过以 RENTALS 表开头并在 CUSTOMER 表中查找单个要返回的行。在这种情况下,可以说根本没有执行子查询。

    在这些极端之间还有其他各种优化。例如,将 RENTAL.CID 列的唯一值构建到 PICKUP='CARY' 行的内存哈希表中,并全面扫描 CUSTOMER TABLE 以探测每一行的哈希表,这将是一个 HASH SEMIJOIN .同样,没有执行可识别的子查询。 (并且 Barmer 建议的查询重写可能会导致这种计划,但也可能会限制优化器查看适合其他数据分布和基数的其他计划)。

    因此,正如其他答案所说,这个问题确实没有实际意义,因为我认为这里有两个重要的教训:

    1. 许多不同的 SQL 语句可以导致相同的执行计划,单个 SQL 语句也可以导致多个执行计划。
    2. 您应该编写在语法上表达您想要的结果的查询,而不是一般地抢占和限制查询优化器的选择。

    第二点很重要,因为它反对某些开发人员避免编写相关子查询(尤其是 EXISTS 或 NOT EXISTS)以提供自己的优化的本能。特别是,考虑到正确/错误的数据分布,用外连接替换 EXISTS 可能不是最理想的。

    要直接回答这个问题,我会说:

    • 0
    • 1
    • CUSTOMERS 中有多少行
    • 可能还有别的东西。

    【讨论】:

      【解决方案3】:

      查询不会以这种方式执行。查询计划器将把它变成一种东西,它将针对另一个查询的结果运行一个查询,而不是一遍又一遍地运行子查询。

      您的查询的查询计划很可能与您进行左连接并检查现有记录的查询相同:

      select c.CID, c.CNAME
      from CUSTOMER c
      left join RENTALS r on c.CID = r.CID and r.PICKUP = 'CARY'
      where r.CID is not null
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-06
        • 2022-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-22
        • 2011-11-23
        • 2015-04-10
        相关资源
        最近更新 更多