为什么链接服务器在CASE表达式中限制为10个分支?


19

为什么这样CASE表达:

SELECT CASE column 
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        ... c -> i
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END [col] 
FROM LinkedServer.database.dbo.table

产生这个结果?

错误消息:消息8180,级别16,状态1,行1的
语句无法准备。
消息125,级别15,状态4,第1行
Case表达式只能嵌套到级别10。

显然,这里没有嵌套CASE表达式,尽管有十多个“分支”。

另一个奇怪的地方。此内联表值函数产生相同的错误:

ALTER FUNCTION [dbo].[fn_MyFunction]
(   
     @var varchar(20)
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT CASE column 
            WHEN 'a' THEN '1' 
            WHEN 'b' THEN '2' 
            ... c -> i
            WHEN 'j' THEN '10' 
            WHEN 'k' THEN '11'  
        END [col] 
    FROM LinkedServer.database.dbo.table
)

但是类似的多语句TVF可以正常工作:

ALTER FUNCTION [dbo].[fn_MyFunction]
(   
    @var varchar(20)
)
RETURNS @result TABLE 
(
    value varchar(max)
)
AS
BEGIN
    INSERT INTO @result
    SELECT CASE column 
            WHEN 'a' THEN '1' 
            WHEN 'b' THEN '2' 
            ... c -> i
            WHEN 'j' THEN '10' 
            WHEN 'k' THEN '11'  
        END [col] 
    FROM LinkedServer.database.dbo.table

RETURN;
END

Answers:


24

显然,这里没有嵌套CASE表达式。

不在查询文本中,否。但是解析器总是将CASE表达式扩展为嵌套形式:

SELECT CASE SUBSTRING(p.Name, 1, 1)
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        WHEN 'c' THEN '3' 
        WHEN 'd' THEN '4' 
        WHEN 'e' THEN '5' 
        WHEN 'f' THEN '6' 
        WHEN 'g' THEN '7' 
        WHEN 'h' THEN '8' 
        WHEN 'i' THEN '9' 
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END
FROM AdventureWorks2012.Production.Product AS p

本地查询计划

该查询是本地查询(无链接服务器),并且Compute Scalar定义以下表达式:

嵌套的CASE表达式

当在本地执行时,这很好,因为解析器看不到CASE深度超过10层的嵌套语句(尽管它确实将其传递给本地查询编译的后续阶段)。

但是,对于链接服务器,可以将生成的文本发送到远程服务器进行编译。如果是这种情况,远程解析器将看到一个嵌套的CASE语句,其深度超过10个级别,并且会出现错误8180。

另一个奇怪的地方。此内联表值函数产生相同的错误

内联函数就地扩展为原始查询文本,因此链接服务器出现相同的错误结果也就不足为奇了。

但是类似的多语句TVF可以正常工作

相似,但不相同。msTVF涉及到的隐式转换varchar(max),以防止将CASE表达式发送到远程服务器。因为CASE本地评估,所以解析器永远不会看到过度嵌套CASE且没有错误。如果将表定义从更改为结果varchar(max)的隐式类型--使用msTVF远程处理表达式,则会出现错误。CASEvarchar(2)

最终,当CASE远程服务器评估过度嵌套时,就会发生该错误。如果CASE未在“远程查询”迭代器中评估,则不会产生错误。例如,以下内容包括CONVERT不远程的,因此即使使用链接服务器也不会发生错误:

SELECT CASE CONVERT(varchar(max), SUBSTRING(p.Name, 1, 1))
        WHEN 'a' THEN '1' 
        WHEN 'b' THEN '2' 
        WHEN 'c' THEN '3' 
        WHEN 'd' THEN '4' 
        WHEN 'e' THEN '5' 
        WHEN 'f' THEN '6' 
        WHEN 'g' THEN '7' 
        WHEN 'h' THEN '8' 
        WHEN 'i' THEN '9' 
        WHEN 'j' THEN '10' 
        WHEN 'k' THEN '11'  
    END
FROM SQL2K8R2.AdventureWorks.Production.Product AS p

案例不远程


6

我的直觉是查询将被重新编写,以使其CASE结构略有不同,例如

CASE WHEN column = 'a' THEN '1' ELSE CASE WHEN column = 'b' THEN '2' ELSE ...

我相信这是您正在使用的任何链接服务器提供程序中的错误(实际上可能是所有错误-我已经看到它针对多个报告)。我也相信您不应该屏住呼吸等待修复,无论是在功能上还是在解释行为的令人困惑的错误消息中-这已经有很长的报道了,涉及链接服务器(自SQL以来就没有多大的热爱了) Server 2000)的影响力,远远少于此令人困惑的错误消息,该错误消息在同样的寿命后仍未得到解决。

正如Paul所指出的,SQL Server正在将您的CASE表达式扩展为嵌套的变量,而链接服务器则不喜欢它。该错误消息令人困惑,但这仅仅是因为表达式的基础转换不是立即可见的(无论如何也不直观)。

一种解决方法(除了您添加到问题中的功能更改之外)是在链接服务器上创建视图或存储过程,并对其进行引用,而不是通过链接服务器提供程序传递完整查询。

另一个(假设您的查询确实如此简单,并且您只希望字母az的数字系数)具有:

SELECT [col] = RTRIM(ASCII([column])-96)
FROM LinkedServer.database.dbo.table;

如果您绝对需要按原样进行操作,建议您直接与支持人员联系并打开案例,尽管我不能保证结果-他们可能只是为您提供此页面上已经可以访问的解决方法。


5

你可以解决这个问题

SELECT COALESCE(
CASE SUBSTRING(p.Name, 1, 1)
    WHEN 'a' THEN '1' 
    WHEN 'b' THEN '2' 
    WHEN 'c' THEN '3' 
    WHEN 'd' THEN '4' 
    WHEN 'e' THEN '5' 
    WHEN 'f' THEN '6' 
    WHEN 'g' THEN '7' 
    WHEN 'h' THEN '8' 
    WHEN 'i' THEN '9' 
    ELSE NULL
END,
CASE SUBSTRING(p.Name, 1, 1)
    WHEN 'j' THEN '10' 
    WHEN 'k' THEN '11'  
END)
FROM SQL2K8R2.AdventureWorks.Production.Product AS p

2

解决此问题的另一种方法是使用基于集合的逻辑,将CASE表达式用左联接(或外部应用)替换到引用表(ref在下面的代码中),该引用表可以是永久表,临时表或派生表/ CTE。如果在多个查询和过程中都需要这样做,则我希望将其作为永久表:

SELECT ref.result_column AS [col] 
FROM LinkedServer.database.dbo.table AS t
  LEFT JOIN
    ( VALUES ('a',  '1'),
             ('b',  '2'), 
             ('c',  '3'),
             ---
             ('j', '10'),
             ('k', '11')
    ) AS ref (check_col, result_column) 
    ON ref.check_col = t.column ;

-4

解决这个问题的一种方法是在when子句中包含测试,即

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...

其实不行 双方SELECT CASE v.V WHEN 'a' THEN 1 WHEN 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);SELECT CASE WHEN v.V = 'a' THEN 1 WHEN v.V = 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);转化为完全一样的执行计划(随时验证自己),其中CASE表达式重新定义为CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END-拼图功能,你可以看到。
Andriy M
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.