¿Por qué los servidores vinculados tienen una limitación de 10 ramas en una expresión CASE?

19

¿Por qué esta CASEexpresión:

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

Producir este resultado?

Mensaje de error: Msg 8180, Nivel 16, Estado 1, Línea
(s) No se pudieron preparar declaraciones.
Mensaje 125, Nivel 15, Estado 4, Línea 1 Las
expresiones de caso solo se pueden anidar al nivel 10.

Claramente, aquí no hay una CASEexpresión anidada , aunque hay más de 10 "ramas".

Otra rareza. Esta función en línea con valores de tabla produce el mismo error:

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
)

Pero un TVF de varias declaraciones similar funciona bien:

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
Andrey
fuente

Respuestas:

24

Claramente no hay una CASEexpresión anidada aquí.

No en el texto de la consulta, no. Pero el analizador siempre expande CASEexpresiones a la forma anidada:

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

Plan de consulta local

Esa consulta es local (sin servidor vinculado) y Compute Scalar define la siguiente expresión:

Expresión CASO anidada

Esto está bien cuando se ejecuta localmente, porque el analizador no ve una CASEdeclaración anidada de más de 10 niveles de profundidad (aunque sí pasa una a las etapas posteriores de la compilación de consultas locales).

Sin embargo, con un servidor vinculado, el texto generado puede enviarse al servidor remoto para su compilación. Si ese es el caso, el analizador remoto ve una CASEdeclaración anidada de más de 10 niveles de profundidad y obtiene el error 8180.

Otra rareza. Esta función en línea con valores de tabla produce el mismo error

La función en línea se expande in situ en el texto de la consulta original, por lo que no sorprende que se produzca el mismo error con el servidor vinculado.

Pero un TVF de varias declaraciones similar funciona bien

Similar, pero no igual. MsTVF implica una conversión implícita a varchar(max), lo que evita que la CASEexpresión se envíe al servidor remoto. Debido a que CASEse evalúa localmente, un analizador nunca ve una anidación excesiva CASEy no hay ningún error. Si cambia la definición de la tabla varchar(max)al tipo implícito deCASE resultado varchar(2), la expresión se remota con msTVF y obtendrá un error.

Finalmente, el error ocurre cuando CASEel servidor remoto evalúa una anidación excesiva . Si CASEno se evalúa en el iterador de consulta remota, no se produce ningún error. Por ejemplo, lo siguiente incluye un CONVERTque no es remoto, por lo que no se produce ningún error a pesar de que se utiliza un servidor vinculado:

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

CASO no remoto

Paul White dice GoFundMonica
fuente
6

Mi presentimiento es que la consulta se está reescribiendo en algún lugar del camino para tener una CASEestructura ligeramente diferente , por ejemplo

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

Creo que este es un error en cualquier proveedor de servidor vinculado que esté utilizando (de hecho, tal vez todos ellos, lo he visto reportado en contra de varios). También creo que no debe contener la respiración esperando una solución, ya sea en la funcionalidad o en el mensaje de error confuso que explica el comportamiento: esto se ha informado durante mucho tiempo e involucra servidores vinculados (que no han tenido mucho amor desde SQL Server 2000), y afecta a muchas menos personas que este mensaje de error confuso , que aún no se ha solucionado después de la misma longevidad.

Como señala Paul , SQL Server está expandiendo suCASE expresión a la variedad anidada, y al servidor vinculado no le gusta. El mensaje de error es confuso, pero solo porque la conversión subyacente de la expresión no es inmediatamente visible (ni intuitiva de ninguna manera).

Una solución alternativa (que no sea el cambio de función que agregó a su pregunta) sería crear una vista o procedimiento almacenado en el servidor vinculado y hacer referencia a eso, en lugar de pasar la consulta completa a través del proveedor del servidor vinculado.

Otro (suponiendo que su consulta sea realmente así de simple, y solo desea el coeficiente numérico de las letras az) es tener:

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

Si realmente necesita que esto funcione tal como está, le sugiero que se ponga en contacto directamente con el soporte y abra un caso, aunque no puedo responder por los resultados, es posible que solo le brinden soluciones a las que ya tiene acceso en esta página.

Aaron Bertrand
fuente
5

puedes evitar esto

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
Nik
fuente
2

Otra solución para este problema es usar una lógica basada en conjuntos, reemplazando la CASEexpresión con una combinación izquierda (o aplicación externa) a una tabla de referencia ( refen el código a continuación), que puede ser permanente, temporal o una tabla / CTE derivada. Si esto es necesario en múltiples consultas y procedimientos, preferiría tener esto como tabla permanente:

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 ;
ypercubeᵀᴹ
fuente
-4

una forma de evitar esto es incluir la prueba en la whencláusula, es decir

case
  when SUBSTRING(p.Name, 1, 1) = 'a' THEN '1'
...
usuario98586
fuente
En realidad no. Ambos SELECT CASE v.V WHEN 'a' THEN 1 WHEN 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);y SELECT CASE WHEN v.V = 'a' THEN 1 WHEN v.V = 'b' THEN 2 END FROM (VALUES ('a'), ('b')) AS v (V);traduzca exactamente el mismo plan de ejecución (siéntase libre de verificarlo usted mismo), donde la expresión CASE se redefine como CASE WHEN [Union1002]='a' THEN (1) ELSE CASE WHEN [Union1002]='b' THEN (2) ELSE NULL END END- con anidamiento, como puede ver.
Andriy M