La expresión CASE devuelve un valor incorrecto cuando se usa CEILING

11

Me he encontrado con un problema en el que una CASEexpresión no devuelve lo que esperaba.

Como prueba, agregué una variable decimal y ejecuté la misma CASEexpresión contra ella y funciona bien, devolviendo los resultados como esperaba (redondeando el valor cuando IsGun=1. Pero cuando ejecuto esa misma CASEexpresión contra otro valor decimal, siempre devuelve el valor con la CEILING()función y nunca devuelve el valor original.

Aquí está el código SQL:

DECLARE @Num decimal(8,2);
    set @Num = 12.54;
    WITH PQ AS
    ( 
        SELECT 
            UPC, 
            Price1, 
            DBID,
            AVG(Price1) OVER (PARTITION BY UPC) AS Price1Avg
        FROM
            vProducts_PriceQty_Union
    )
    SELECT 
        PQ.UPC,
        PQ.Price1,
        PQ.Price1Avg,
        (CASE WHEN p.IsGun = 1 THEN CEILING(@Num) ELSE @Num END) AS UsingVar,
        CAST(
            (CASE WHEN P.IsGun = 1 THEN CEILING(PQ.Price1Avg) ELSE PQ.Price1 END)
             AS NUMERIC(8,2))
        AS PriceAdj,
        PQ.DBID,
        P.IsGun
    FROM
        PQ
     INNER JOIN
        products P ON PQ.UPC = P.UPC

Aquí hay un fragmento de los resultados:

UPC             Price1      Price1Avg   UsingVar    PriceAdj    DBID  IsGun
942000899195    14.9900     14.990000   12.54       15.00       1       0
980420671300    29.9900     29.990000   12.54       30.00       1       0
980420671310    29.9900     29.990000   12.54       30.00       1       0
980426713020    29.9900     29.990000   12.54       30.00       1       0
980426713120    29.9900     29.990000   12.54       30.00       1       0
000998622130    319.0000    319.000000  13.00       319.00      1       1
000998624730    314.0000    314.000000  13.00       314.00      1       1
000998624970    419.0000    419.000000  13.00       419.00      1       1
008244284754    1015.0000   1015.000000 13.00       1015.00     2       1
010633012288    267.0000    267.000000  13.00       267.00      6       1

Y aquí están los datos que provienen de vProducts_PriceQty_Union :

UPC             Price1  Price2  Quantity    DBID
942000899195    14.9900 0.0000  2.00        1
980420671300    29.9900 0.0000  3.00        1
980420671310    29.9900 0.0000  1.00        1
980426713020    29.9900 0.0000  2.00        1
980426713120    29.9900 0.0000  1.00        1

Como puede ver en los primeros cinco, donde IsGun = 0, la primera CASEexpresión que usa la variable fija devuelve el valor UsingVar como lo que esperaríamos, 12.54. Y para los últimos cinco, también devuelve el valor que esperaríamos, 13.

Pero en la segunda CASEexpresión (exactamente la misma lógica), PriceAdj usa la CEILINGfunción en cada una de ellas, independientemente de si IsGun = 1 o no.

¿Por qué la consulta no devuelve los resultados esperados?

En algunas de las tablas utilizadas para la vista de unión, los tipos de datos para Price1 y Price2 eran smallmoney y decimal (8,2) . Desde entonces los he cambiado todos a decimales (8,2) , pero eso no afectó los resultados.

Rodney G
fuente

Respuestas:

11

Para reproducir el problema:

SELECT *, (CASE
    WHEN IsGun=1 THEN CEILING(Price1Avg)
    ELSE Price1 END)
FROM (
    SELECT UPC, IsGun, Price1,
           AVG(CAST(Price1 AS numeric(8, 2))) OVER (PARTITION BY UPC) AS Price1Avg
    FROM (
        VALUES ('A', 0, 14.99),
               ('B', 0, 29.99),
               ('C', 1, 319.00),
               ('D', 1, 314.00)
        ) AS x(UPC, IsGun, Price1)
    ) AS sub;

Lo que sucede aquí es que CEILING(PQ.Price1Avg)produce a numeric(38, 0).

De acuerdo con la documentación , el tipo de salida de CEILING()es del mismo tipo de datos base que la entrada, aunque la escala (el número de decimales) puede cambiar, que es lo que sucede aquí.

  • La AVG()función, en mis pruebas, regresa numeric(38, 6).
  • La CEILING()función en esa columna, sin embargo, genera numeric(38, 0):

Para verificar:

SELECT CEILING(CAST(123.45 AS numeric(38, 6)))

Como solución alternativa, puede convertir explícitamente el resultado de la CEILING()función, lo que debería proporcionarle los resultados correctos:

SELECT *, (CASE
    WHEN IsGun=1 THEN CAST(CEILING(Price1Avg) AS numeric(8, 2)) -- Explicit CAST.
    ELSE Price1 END)
FROM (
    SELECT UPC, IsGun, Price1,
           AVG(CAST(Price1 AS numeric(8, 2))) OVER (PARTITION BY UPC) AS Price1Avg
    FROM (
        VALUES ('A', 0, 14.99),
               ('B', 0, 29.99),
               ('C', 1, 319.00),
               ('D', 1, 314.00)
        ) AS x(UPC, IsGun, Price1)
    ) AS sub;
Daniel Hutmacher
fuente
También debe tener en cuenta que las declaraciones de casos no pueden devolver múltiples tipos diferentes, pero una expresión IIF sí, por lo que puede ser aconsejable.
Adam Martin
2
@ AdamMartin eso no es correcto. Un iifse expande a case. Se devuelve el tipo con la mayor prioridad de tipos de datos de las dos opciones. No es posible que ninguna expresión devuelva el tipo de datos X en una fila y el tipo de datos Y en otra fila para la misma columna.
Martin Smith
@Daniel, lo más útil. Tan pronto como CAST (x AS NUMERIC (8,2)) para cada salida, devolvió el resultado que estaba buscando. ¡Muchas gracias a ti y a esta comunidad!
Rodney G