如何将 CASE 表达式重写为短路评估

jer*_*ech 2 performance sql-server execution-plan query-performance

我想知道是否有任何方法可以重写查询中的表达式(只是表达式,而不是整个查询)以短路 THEN 阶段的无用评估?

演示数据:

CREATE TABLE #Docs (
    ID INT NOT NULL
    ,DocType TINYINT NOT NULL
    );

CREATE TABLE #DocsItems (
    IDDocs INT NOT NULL
    ,Amount NUMERIC(19,6)
    );

INSERT INTO #Docs(ID, DocType) VALUES(1,1),(2,1),(3,2),(4,2),(5,2),(6,2);
INSERT INTO #DocsItems(IDDocs,Amount) VALUES(3,50.),(3,25.),(3,33.),(4,44.),(4,123.),(6,11.);
Run Code Online (Sandbox Code Playgroud)

主题查询:

SELECT
    -- expression
    SumAmount = CASE 
                    WHEN D.DocType <> 1 THEN (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID)
                END
FROM #Docs D
WHERE D.DocType = 1  -- so CASE condition evaluates to False
Run Code Online (Sandbox Code Playgroud)

查询计划: 在此处输入图片说明

如果我将查询(故意)重写为:

SELECT
    -- expression
    SumAmount = CASE 
                    WHEN 2 = 1 /* rewrite*/  THEN (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID)
                END
FROM #Docs D
WHERE D.DocType = 1
Run Code Online (Sandbox Code Playgroud)

该计划按照原始查询中应该/可能/会的方式进行: 在此处输入图片说明

Ran*_*gen 5

目前正在发生什么

运行查询时,不会在运行时评估表扫描、流聚合和计算标量运算符。

在此处输入图片说明

在此处输入图片说明


为什么会发生

apply NL 连接意味着对于 中的每一行#Docs,返回#Docsitems与谓词匹配的行。这个谓词应该是WHERE IDDocs = D.ID

但是EXPR1007select(实际的 case 语句)旁边的计算标量运算符( EXPR1005) 仅在Doctype <> 1. 如您所知,这不可能发生,它们都会返回NULL

计算 NL 和 SELECT 之间的标量:

在此处输入图片说明

计算连接内侧的标量:

在此处输入图片说明

这一切似乎都是由于 CASE 语句的功能和删除文字的方式。( CASE 2 = 1vs. 的区别CASE WHEN D.DocType <> 1)


解决

如果您将查询更改为:

SELECT
    -- expression
    SumAmount = 
                     (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID AND D.DocType <> 1 )

FROM #Docs D
WHERE D.DocType = 1  -- so CASE condition evaluates to False
Run Code Online (Sandbox Code Playgroud)

你应该得到你想要的执行计划:

在此处输入图片说明


删除正在更改重写计划的 SELonLOJ 规则。

猜测

可以通过添加提示来恢复为接近 CASE WHEN 查询而应用的规则: OPTION( QUERYRULEOFF SELonLOJ )

SELECT
    -- expression
    SumAmount = 
                     (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID AND D.DocType <> 1 )

FROM #Docs D
WHERE D.DocType = 1  -- so CASE condition evaluates to False
OPTION( QUERYRULEOFF SELonLOJ   );
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

显示与 case 语句相同的情况(减去过滤器和左连接),但没有发生运行时消除。

在此处输入图片说明

&

在此处输入图片说明


case when关闭它时似乎更接近的另一个规则是JoinPredNorm

SELECT
    -- expression
    SumAmount = 
                     (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID AND D.DocType <> 1 )

FROM #Docs D
WHERE D.DocType = 1  -- so CASE condition evaluates to False
OPTION( QUERYRULEOFF JoinPredNorm   )
Run Code Online (Sandbox Code Playgroud)

在#Docs 表上按预期进行过滤。

在此处输入图片说明