为什么优化器决定 self-join a table?

Why does the optimizer decide to self-join a table?

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

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
    );

它returns其中未完成任务数量最多的项目的标题。

这是它的执行计划:

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

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

除了上面的评论之外,当您不止一次引用 CTE 时,有一个启发式告诉优化器具体化 CTE,这就是为什么您会看到临时 table 转换。

关于此查询的其他一些 comments/questions。我假设关系是一个 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  

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

 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