为什么优化器决定自联接一个表?

问题描述

我正在分析如下所示的查询

WITH Project_UnfinishedCount AS (
    SELECT P.Title AS title,COUNT(T.ID) AS cnt
    FROM PROJECT P LEFT JOIN TASK T on P.ID = T.ID_PROJECT AND T.ACTUAL_FINISH_DATE IS NULL 
    GROUP BY P.ID,P.TITLE                                                                  
)
SELECT Title
FROM Project_UnfinishedCount
WHERE cnt = (
    SELECT MAX(cnt)
    FROM Project_UnfinishedCount
    );

它返回一个包含最多未完成任务的项目的标题

这是它的执行计划:

enter image description here

我想知道为什么它的步骤 6-8 看起来像项目表的自联接?并将连接的结果存储为视图,但视图,根据行和字节列与项目表相同。他为什么要这样做?

我还想知道 2 和 1 步骤代表什么。我猜,2 存储我的 CTE 的结果以在步骤 10-14 中使用它,1 从视图中删除没有子查询返回的“cnt”值的行,这是一个正确的猜测吗?

解决方法

除了上面的注释之外,当您多次引用 CTE 时,有一种启发式方法会告诉优化器物化 CTE,这就是您看到临时表转换的原因。

关于此查询的其他一些评论/问题。我假设关系是一个 PROJECT 可以有 0 个或多个任务,并且每个 TASK 只针对一个 PROJECT。在那种情况下,我想知道为什么你有一个外连接?此外,您正在加入 ACTUAL_FINISH_DATE 列。这意味着,如果您有一个项目,其中所有任务都已完成,那么外连接将具体化不匹配的行,这将使您的查询结果看起来表明有 1 个未完成的任务。所以我认为你的 CTE 应该看起来更像:

SELECT P.Title AS title,COUNT(T.ID) AS cnt
FROM PROJECT P 
JOIN TASK T on P.ID = T.ID_PROJECT
WHERE T.ACTUAL_FINISH_DATE IS NULL 
GROUP BY P.ID,P.TITLE  

话虽如此,这些“在组内查找匹配项(计数、最大值等)”类型的查询在编写为窗口函数时通常更有效。这样你就可以消除自连接。当您有数百万或数十亿行时,这可能会产生很大的性能差异。例如,您的查询可以重写为:

 SELECT TITLE,CNT
 FROM (
    SELECT P.Title AS title,COUNT(T.ID) AS cnt,RANK() OVER( ORDER BY COUNT(*) DESC ) RNK
    FROM PROJECT P
    JOIN TASK T on P.ID = T.ID_PROJECT
    WHERE T.ACTUAL_FINISH_DATE IS NULL 
    GROUP BY P.ID,P.TITLE  
      )
  WHERE RNK=1