L'espressione CASE restituisce un valore errato quando si utilizza SOFFITTO


11

Ho riscontrato un problema in cui CASEun'espressione non restituisce ciò che mi aspetto.

Come test, ho aggiunto una variabile decimale ed eseguito la stessa CASEespressione contro di essa e funziona bene, restituendo i risultati come mi aspettavo (arrotondando il valore quando IsGun=1. Ma quando eseguo quella stessa CASEespressione con un altro valore decimale, restituisce sempre il valore con la CEILING()funzione e non restituisce mai il valore originale.

Ecco il codice 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

Ecco uno snippet dei risultati:

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

Ed ecco i dati che provengono da 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

Come puoi vedere dai primi cinque, dove IsGun = 0, la prima CASEespressione che utilizza la variabile fissa restituisce il valore UsingVar come ci aspetteremmo, 12.54. E per gli ultimi cinque, restituisce anche il valore che ci aspetteremmo, 13.

Ma nella seconda CASEespressione (esattamente la stessa logica), PriceAdj utilizza la CEILINGfunzione su ognuno di essi, indipendentemente dal fatto che IsGun = 1 o meno.

Perché la query non restituisce i risultati previsti?

In alcune delle tabelle utilizzate per la vista unione i tipi di dati per Price1 e Price2 erano smallmoney e decimal (8,2) . Da allora li ho cambiati tutti in decimali (8,2) , ma ciò non ha influito sui risultati.

Risposte:


11

Per riprodurre il 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;

Quello che succede qui è che CEILING(PQ.Price1Avg)produce un numeric(38, 0).

Secondo la documentazione , il tipo di output di CEILING()è dello stesso tipo di dati di base dell'input, sebbene la scala (il numero di decimali) possa cambiare, che è ciò che accade qui.

  • La AVG()funzione, nei miei test, ritorna numeric(38, 6).
  • La CEILING()funzione su quella colonna, tuttavia, genera numeric(38, 0):

Verificare:

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

Per ovviare al problema, è possibile convertire esplicitamente l'output della CEILING()funzione, che dovrebbe fornire i risultati corretti:

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;

Dovrebbe anche notare che le dichiarazioni di casi non possono restituire più tipi diversi, ma un'espressione IIF può, quindi ciò può essere consigliabile.
Adam Martin,

2
@AdamMartin che non è corretto. Viene iifespanso a case. Si restituisce il tipo con la più grande tipo di dati la precedenza dalle due opzioni. Non è possibile per nessuna espressione restituire il tipo di dati X in una riga e il tipo di dati Y in un'altra riga per la stessa colonna.
Martin Smith,

@ Daniele, molto utile. Non appena ho CAST (x AS NUMERIC (8,2)) per ogni output, ha restituito il risultato che stavo cercando. Mille grazie a te e questa community!
Rodney G,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.