嵌套 CASE 的 T-SQL 替代方案以获得更好的性能?

Posted

技术标签:

【中文标题】嵌套 CASE 的 T-SQL 替代方案以获得更好的性能?【英文标题】:T-SQL alternatives to nested CASE for better performance? 【发布时间】:2015-05-13 15:14:28 【问题描述】:

我有一个T-SQL 查询,它的性能非常糟糕,以至于它超时了。罪魁祸首是这两个带有嵌入式查询的嵌套 CASE 语句:

SELECT
  CASE
    WHEN b.month_type = (CASE
        WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) >= 8 THEN 'Current Month BD2'
        ELSE (CASE
            WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) < 8 AND
              (SELECT
                MAX(b.cal_start_date)
              FROM factbillingcollectionhistory a
              JOIN dimdateperiod b
                ON a.fiscal_month = b.fsc_period)
              <> (SELECT
                MAX(cal_start_date)
              FROM dimdateperiod) THEN 'Current Reporting Month'
            ELSE 'Current Month BD2'
          END)
      END) THEN a.BILLINGS_BUDGET
    ELSE 0
  END
  AS BILLINGS_BUDGET,
  CASE
    WHEN b.month_type = (CASE
        WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) >= 8 THEN 'Current Month BD2'
        ELSE (CASE
            WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) < 8 AND
              (SELECT
                MAX(b.cal_start_date)
              FROM factbillingcollectionhistory a
              JOIN dimdateperiod b
                ON a.fiscal_month = b.fsc_period)
              <> (SELECT
                MAX(cal_start_date)
              FROM dimdateperiod) THEN 'Current Reporting Month'
            ELSE 'Current Month BD2'
          END)
      END) THEN a.COLLECTION_GOALS
    ELSE 0
  END
  AS COLLECTION_GOALS

CURRENT_BUSINESSDAY 函数的作用正是它所描述的......标识报告期间的当前工作日。

CASE 的逻辑是根据我们在报告周期中所处的位置以及我们是否收到更新的目标文件来返回目标值。如果还不是 BD8,请检查我们是否收到了新文件(通过比较最大日期)。如果我们收到了,则将该值返回到报告中,否则返回上个月的值。如果在 BD8 之后,我们仍然没有新文件,它应该返回“0”,这将使我们的过程失败,并让我们知道他们没有按时提供数据。

是否有更有效的方法来编写此逻辑以防止查询超时?请记住,此脚本用于在表格模型中构建表格,因此只有 SELECT 起作用……没有变量声明或任何类似的东西。

想法?

【问题讨论】:

我猜是标量值函数的原因,你真的需要CURRENT_BUSINESSDAY吗?函数是一个黑盒子,每行都需要运行一次,不能被查询计划引擎优化。你有WHERE 过滤记录吗? 同意@TimSchmelter 100%。嵌套 case 表达式不是性能问题,但标量函数是。 我不知道 SQL Server 是否真的会评估每一行的所有 current_businessday 调用,因为它们都是针对当前日期的。不确定我是否理解为什么不能使用变量,但我会尝试将函数的结果放入变量中,并用该变量替换对函数的所有调用。如果这解决了性能问题,那么至少我们知道问题是什么。 奇怪的是 GetDate() 在查询中被视为runtime constant function。 (Ref.) 在变量中捕获当前日期/时间或Current_BusinessDay( GetDate() ) 通常更清晰,然后根据需要使用该值。这在多个语句中更为重要,例如在存储过程中,值可能会从一个语句更改为下一个语句。 【参考方案1】:

由于 COLLECTION_GOALS 和 BILLINGS_BUDGET 中的案例逻辑相同,我建议将逻辑从内联子查询中移到主 FROM 子句中,如下所示:

SELECT CASE WHEN b.month_type = z.month_type
           THEN a.BILLINGS_BUDGET
           ELSE 0
       END AS BILLINGS_BUDGET,
       CASE WHEN b.month_type = z.month_type
           THEN a.COLLECTION_GOALS
           ELSE 0
       END AS COLLECTION_GOALS
FROM (SELECT CASE WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) >= 8
                 THEN 'Current Month BD2'
                 ELSE (CASE WHEN dbo.CURRENT_BUSINESSDAY(GETDATE()) < 8 AND 
                                 (SELECT max(b.cal_start_date) 
                                         FROM factbillingcollectionhistory a
                                         JOIN dimdateperiod b
                                           ON a.fiscal_month = b.fsc_period) <> (SELECT max(cal_start_date) FROM dimdateperiod)
                           THEN 'Current Reporting Month'
                           ELSE 'Current Month BD2'
                       END)
             END month_type) z
CROSS JOIN
/*... Rest of query */

这应该导致它在每个查询中被评估一次,而不是为查询返回的每一行计算两次。

【讨论】:

以上是关于嵌套 CASE 的 T-SQL 替代方案以获得更好的性能?的主要内容,如果未能解决你的问题,请参考以下文章

具有相同键的(嵌套)字典的 Pythonic 替代方案?

如何在 ijkplayer 中更改 HLS 质量以获得颤振或替代解决方案?

java,多层for()循环,if()else嵌套分别用啥替代?

CASE 表达式 WHEN 的更短替代方案?

Java 中 Switch Case 的替代方案

使用 CASE 和 GROUP BY 的动态替代方案