A expressão CASE retorna um valor incorreto ao usar CEILING

11

Corri para um problema em que uma CASEexpressão não retorna o que eu esperava.

Como teste, adicionei uma variável decimal , executei a mesma CASEexpressão e funcionou bem, retornando os resultados como eu esperava (arredondando o valor quando IsGun=1. Mas quando executo a mesma CASEexpressão em outro valor decimal, ela sempre retorna o valor com a CEILING()função e nunca retorna o valor original.

Aqui está o 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

Aqui está um trecho dos 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

E aqui estão os dados 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 você pode ver nos cinco primeiros, onde IsGun = 0, a primeira CASEexpressão usando a variável fixa retorna o valor UsingVar como seria de esperar, 12,54. E nos últimos cinco, ele também retorna o valor que esperaríamos, 13.

Mas na segunda CASEexpressão (exatamente a mesma lógica), o PriceAdj usa a CEILINGfunção em cada uma delas, independentemente de IsGun = 1 ou não.

Por que a consulta não está retornando os resultados esperados?

Em algumas das tabelas usadas para a exibição da união, os tipos de dados para Preço1 e Preço2 eram dinheiro pequeno e decimal (8,2) . Desde então, mudei todos para serem decimais (8,2) , mas isso não afetou os resultados.

Rodney G
fonte

Respostas:

11

Para reproduzir o 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;

O que acontece aqui é que CEILING(PQ.Price1Avg)produz a numeric(38, 0).

De acordo com a documentação , o tipo de saída de CEILING()é do mesmo tipo de dados base que a entrada, embora a escala (o número de casas decimais) possa mudar, o que acontece aqui.

  • A AVG()função, nos meus testes, retorna numeric(38, 6).
  • A CEILING()função nessa coluna, no entanto, gera numeric(38, 0):

Verificar:

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

Como solução alternativa, você pode converter explicitamente a saída da CEILING()função, que deve fornecer os resultados corretos:

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
fonte
Observe também que as instruções de caso não podem retornar vários tipos diferentes, mas uma expressão IIF pode, portanto, isso pode ser aconselhável.
Adam Martin
2
@AdamMartin que não está correto. Um iifé expandido para case. Ele retorna o tipo com o maior tipo de dados precedência das duas opções. Não é possível que nenhuma expressão retorne o tipo de dados X em uma linha e o tipo de dados Y em outra linha para a mesma coluna.
Martin Smith
@ Daniel, mais útil. Assim que eu CAST (x AS NUMERIC (8,2)) para cada saída, ele retornou o resultado que estava procurando. Muito obrigado a você e a esta comunidade!
Rodney G