如何在生成条件和的大型查询中消除 CROSS JOIN?

How can I eliminate CROSS JOIN in a large query that produces conditional sums?

我有一个生成 529,032 行的查询,我将其存储到名为 EFOT_DISTINCT_COMBOS.

的 table 中

(这曾经是物化视图,但我们的服务器管理员建议我们改用纯 tables,并使用存储过程手动 "refresh" 来自查询的数据。)

接下来我需要用它做的是 CROSS JOIN,它遇到了一个主要问题......服务器锁定并挂起,没有做任何工作......(库缓存锁定?)
所以看起来我需要替换查询中的 CROSS JOIN,并找到一种更好的方法来编写此查询。

这个查询看起来像一场噩梦,但它实际上比看起来简单得多 - 它只是计算一些简单的条件总计。
(几个月前,我曾尝试为此使用 window 函数,但是 window 函数破坏了一些东西(我不记得具体是什么)所以我出于某种原因不得不恢复到简单的聚合。)

我们正在 CROSS JOINing 的 table 称为 SNAPSHOTS,它非常短 table - 仅包含 44 行(它不会增长大得多,最终可能最多 80-100 行)。
CROSS JOIN.

生成 529,032 x 44 = 23,277,408 行

我试图通过在底部添加 HAVING COUNT(ef.TIMESTAMP) > 0 来减少这种足迹,这可能会删除 75% 的结果(我不一定需要)。
这肯定有帮助,但查询仍然无法在服务器上执行。

SELECT
    ss.TIMESTAMP,
    ss.TIMESTAMP_DATE,
    fc.FISCAL_QUARTER,
    fc.LOB,
    fc.FREQUENCY,
    fc.EDGE_VP,
    fc.EDGE_RM,
    fc.EDGE_ASM,

    NVL(SUM(ef.PIPELINE),0) AS PIPELINE,
    NVL(SUM(ef.BEST),0) AS BEST,
    NVL(SUM(ef.FORECAST),0) AS FORECAST,
    NVL(SUM(ef.CLOSED),0) AS CLOSED,

    CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PIPELINE_DIFF),0)
                         WHEN 'D' THEN NVL(SUM(ef.D_PIPELINE_DIFF),0)
                         WHEN 'W' THEN NVL(SUM(ef.W_PIPELINE_DIFF),0)
                         WHEN 'M' THEN NVL(SUM(ef.M_PIPELINE_DIFF),0)
                         WHEN 'Q' THEN NVL(SUM(ef.Q_PIPELINE_DIFF),0)
                         WHEN 'Y' THEN NVL(SUM(ef.Y_PIPELINE_DIFF),0) END AS PIPELINE_CHANGE,

    CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.BEST_DIFF),0)
                         WHEN 'D' THEN NVL(SUM(ef.D_BEST_DIFF),0)
                         WHEN 'W' THEN NVL(SUM(ef.W_BEST_DIFF),0)
                         WHEN 'M' THEN NVL(SUM(ef.M_BEST_DIFF),0)
                         WHEN 'Q' THEN NVL(SUM(ef.Q_BEST_DIFF),0)
                         WHEN 'Y' THEN NVL(SUM(ef.Y_BEST_DIFF),0) END AS BEST_CHANGE,

    CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.FORECAST_DIFF),0)
                         WHEN 'D' THEN NVL(SUM(ef.D_FORECAST_DIFF),0)
                         WHEN 'W' THEN NVL(SUM(ef.W_FORECAST_DIFF),0)
                         WHEN 'M' THEN NVL(SUM(ef.M_FORECAST_DIFF),0)
                         WHEN 'Q' THEN NVL(SUM(ef.Q_FORECAST_DIFF),0)
                         WHEN 'Y' THEN NVL(SUM(ef.Y_FORECAST_DIFF),0) END AS FORECAST_CHANGE,

    CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.CLOSED_DIFF),0)
                         WHEN 'D' THEN NVL(SUM(ef.D_CLOSED_DIFF),0)
                         WHEN 'W' THEN NVL(SUM(ef.W_CLOSED_DIFF),0)
                         WHEN 'M' THEN NVL(SUM(ef.M_CLOSED_DIFF),0)
                         WHEN 'Q' THEN NVL(SUM(ef.Q_CLOSED_DIFF),0)
                         WHEN 'Y' THEN NVL(SUM(ef.Y_CLOSED_DIFF),0) END AS CLOSED_CHANGE,

    CASE WHEN CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_PIPELINE),0)
                                   WHEN 'D' THEN NVL(SUM(ef.PREV_D_PIPELINE),0)
                                   WHEN 'W' THEN NVL(SUM(ef.PREV_W_PIPELINE),0)
                                   WHEN 'M' THEN NVL(SUM(ef.PREV_M_PIPELINE),0)
                                   WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_PIPELINE),0)
                                   WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_PIPELINE),0) END = 0 THEN 0
                                                                                        ELSE 100 * CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PIPELINE_DIFF),0)
                                                                                                                        WHEN 'D' THEN NVL(SUM(ef.D_PIPELINE_DIFF),0)
                                                                                                                        WHEN 'W' THEN NVL(SUM(ef.W_PIPELINE_DIFF),0)
                                                                                                                        WHEN 'M' THEN NVL(SUM(ef.M_PIPELINE_DIFF),0)
                                                                                                                        WHEN 'Q' THEN NVL(SUM(ef.Q_PIPELINE_DIFF),0)
                                                                                                                        WHEN 'Y' THEN NVL(SUM(ef.Y_PIPELINE_DIFF),0) END

                                                                                                 / CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_PIPELINE),0)
                                                                                                                        WHEN 'D' THEN NVL(SUM(ef.PREV_D_PIPELINE),0)
                                                                                                                        WHEN 'W' THEN NVL(SUM(ef.PREV_W_PIPELINE),0)
                                                                                                                        WHEN 'M' THEN NVL(SUM(ef.PREV_M_PIPELINE),0)
                                                                                                                        WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_PIPELINE),0)
                                                                                                                        WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_PIPELINE),0) END END AS PIPELINE_PERCENT_CHANGE,

    CASE WHEN CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_BEST),0)
                                   WHEN 'D' THEN NVL(SUM(ef.PREV_D_BEST),0)
                                   WHEN 'W' THEN NVL(SUM(ef.PREV_W_BEST),0)
                                   WHEN 'M' THEN NVL(SUM(ef.PREV_M_BEST),0)
                                   WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_BEST),0)
                                   WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_BEST),0) END = 0 THEN 0
                                                                                    ELSE 100 * CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.BEST_DIFF),0)
                                                                                                                    WHEN 'D' THEN NVL(SUM(ef.D_BEST_DIFF),0)
                                                                                                                    WHEN 'W' THEN NVL(SUM(ef.W_BEST_DIFF),0)
                                                                                                                    WHEN 'M' THEN NVL(SUM(ef.M_BEST_DIFF),0)
                                                                                                                    WHEN 'Q' THEN NVL(SUM(ef.Q_BEST_DIFF),0)
                                                                                                                    WHEN 'Y' THEN NVL(SUM(ef.Y_BEST_DIFF),0) END

                                                                                             / CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_BEST),0)
                                                                                                                    WHEN 'D' THEN NVL(SUM(ef.PREV_D_BEST),0)
                                                                                                                    WHEN 'W' THEN NVL(SUM(ef.PREV_W_BEST),0)
                                                                                                                    WHEN 'M' THEN NVL(SUM(ef.PREV_M_BEST),0)
                                                                                                                    WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_BEST),0)
                                                                                                                    WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_BEST),0) END END AS BEST_PERCENT_CHANGE,

    CASE WHEN CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_FORECAST),0)
                                   WHEN 'D' THEN NVL(SUM(ef.PREV_D_FORECAST),0)
                                   WHEN 'W' THEN NVL(SUM(ef.PREV_W_FORECAST),0)
                                   WHEN 'M' THEN NVL(SUM(ef.PREV_M_FORECAST),0)
                                   WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_FORECAST),0)
                                   WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_FORECAST),0) END = 0 THEN 0
                                                                                        ELSE 100 * CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.FORECAST_DIFF),0)
                                                                                                                        WHEN 'D' THEN NVL(SUM(ef.D_FORECAST_DIFF),0)
                                                                                                                        WHEN 'W' THEN NVL(SUM(ef.W_FORECAST_DIFF),0)
                                                                                                                        WHEN 'M' THEN NVL(SUM(ef.M_FORECAST_DIFF),0)
                                                                                                                        WHEN 'Q' THEN NVL(SUM(ef.Q_FORECAST_DIFF),0)
                                                                                                                        WHEN 'Y' THEN NVL(SUM(ef.Y_FORECAST_DIFF),0) END

                                                                                                 / CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_FORECAST),0)
                                                                                                                        WHEN 'D' THEN NVL(SUM(ef.PREV_D_FORECAST),0)
                                                                                                                        WHEN 'W' THEN NVL(SUM(ef.PREV_W_FORECAST),0)
                                                                                                                        WHEN 'M' THEN NVL(SUM(ef.PREV_M_FORECAST),0)
                                                                                                                        WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_FORECAST),0)
                                                                                                                        WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_FORECAST),0) END END AS FORECAST_PERCENT_CHANGE,

    CASE WHEN CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_CLOSED),0)
                                   WHEN 'D' THEN NVL(SUM(ef.PREV_D_CLOSED),0)
                                   WHEN 'W' THEN NVL(SUM(ef.PREV_W_CLOSED),0)
                                   WHEN 'M' THEN NVL(SUM(ef.PREV_M_CLOSED),0)
                                   WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_CLOSED),0)
                                   WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_CLOSED),0) END = 0 THEN 0
                                                                                      ELSE 100 * CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.CLOSED_DIFF),0)
                                                                                                                      WHEN 'D' THEN NVL(SUM(ef.D_CLOSED_DIFF),0)
                                                                                                                      WHEN 'W' THEN NVL(SUM(ef.W_CLOSED_DIFF),0)
                                                                                                                      WHEN 'M' THEN NVL(SUM(ef.M_CLOSED_DIFF),0)
                                                                                                                      WHEN 'Q' THEN NVL(SUM(ef.Q_CLOSED_DIFF),0)
                                                                                                                      WHEN 'Y' THEN NVL(SUM(ef.Y_CLOSED_DIFF),0) END

                                                                                               / CASE fc.FREQUENCY WHEN '%' THEN NVL(SUM(ef.PREV_CLOSED),0)
                                                                                                                      WHEN 'D' THEN NVL(SUM(ef.PREV_D_CLOSED),0)
                                                                                                                      WHEN 'W' THEN NVL(SUM(ef.PREV_W_CLOSED),0)
                                                                                                                      WHEN 'M' THEN NVL(SUM(ef.PREV_M_CLOSED),0)
                                                                                                                      WHEN 'Q' THEN NVL(SUM(ef.PREV_Q_CLOSED),0)
                                                                                                                      WHEN 'Y' THEN NVL(SUM(ef.PREV_Y_CLOSED),0) END END AS CLOSED_PERCENT_CHANGE -- ,

    -- COUNT(ef.TIMESTAMP) AS DEAL_SS_COUNT

FROM       EFOT_DISTINCT_COMBOS    fc
CROSS JOIN SNAPSHOTS ss
LEFT JOIN  EDGE_FORECAST_OVER_TIME ef

ON  ef.TIMESTAMP = ss.TIMESTAMP
AND ef.fq_selection = NVL(fc.FISCAL_QUARTER,'%')
AND ef.lob_selection = NVL(fc.LOB,'%')
AND ((fc.FREQUENCY = 'D' AND ef.FREQUENCY in('D', 'W', 'M', 'Q', 'Y') AND ef.TIMESTAMP_DATE >= TRUNC(LOCALTIMESTAMP) - INTERVAL '1' MONTH)
  OR (fc.FREQUENCY = 'W' AND TO_CHAR(ef.TIMESTAMP, 'fmDAY') = 'MONDAY' AND ef.TIMESTAMP_DATE >= TRUNC(LOCALTIMESTAMP) - INTERVAL '3' MONTH)
  OR (fc.FREQUENCY = 'M' AND ef.FREQUENCY in('M', 'Q', 'Y') AND ef.TIMESTAMP_DATE >= TRUNC(LOCALTIMESTAMP) - INTERVAL '1' YEAR)
  OR (fc.FREQUENCY = 'Q' AND ef.FREQUENCY in('Q', 'Y'))
  OR ef.FREQUENCY LIKE NVL(fc.FREQUENCY,'%'))
AND ef.EDGE_VP LIKE NVL(fc.EDGE_VP,'%')
AND ef.EDGE_RM LIKE NVL(fc.EDGE_RM,'%')
AND ef.EDGE_ASM LIKE NVL(fc.EDGE_ASM,'%')

WHERE ss.TABLE_NAME = 'EDGE_FORECAST' 

GROUP BY ss.TIMESTAMP      ,
         ss.TIMESTAMP_DATE ,
         fc.FISCAL_QUARTER ,
         fc.LOB            ,
         fc.FREQUENCY      ,
         fc.EDGE_VP        ,
         fc.EDGE_RM        ,
         fc.EDGE_ASM 

HAVING COUNT(ef.TIMESTAMP) > 0;

接下来,尝试将此查询拆分为两个查询。

第一个查询运行良好,执行时间约为 12 分钟。
但是第二个查询变得更加复杂,运行 将近 6 个小时,最终失败:
ORA-01555: snapshot too old.

有没有更好的方法来编写此代码,以便服务器真正完成工作?
在我们添加所有 LOB 过滤器之前,这个查询以前是有效的(我猜一个额外的过滤器将它推到边缘,就执行计划的复杂性而言)。

(PasteBin,因为这些查询对于 Whosebug 来说太长了。)
1st Query:

2nd Query

戈登·利诺夫评论道:
"The cost of an expensive query is almost always due to data movement -- joins, aggregations, and sorting. There are exceptions. For instance, long text fields or complex JSON can add significant overhead, but it general it is data movement."

此外,德尔评论道:
"A few things after a quick look, I think your query is too verbose. You are doing a LEFT JOIN on the ef table. But then your HAVING clause is checking that a COUNT on that table is > 0. Just switch it to an INNER JOIN. After that , you may be able to remove some of those NVL functions you have throughout the code if the column is required in the ef table."

这两条评论都提到了连接,它可能是性能问题的原因。

我听从了 Del 的建议,删除了 HAVING 子句,并将 LEFT JOIN 转换为 INNER JOIN
我还尽可能删除了大部分 NVL 函数,或者合并它们以使查询更简单。

仅通过这两项更改,查询现在可以在 15 分钟内成功执行,而不是 运行 数小时并最终失败。
我敢打赌 LEFT JOIN 是 99% 的问题。

感谢所有花时间阅读我所有荒谬代码并给我提示的人!