1

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

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

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


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

4

3 回答 3

4

这个问题没有正确的理论答案。智能查询优化器可以将查询转换为 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

现在子查询只执行一次。

于 2013-09-14T09:00:16.137 回答
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
  • 可能是别的东西。
于 2013-09-14T10:05:09.183 回答
1

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

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

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
于 2013-09-14T08:58:04.850 回答