L'expression CASE renvoie une valeur incorrecte lors de l'utilisation de CEILING


11

J'ai rencontré un problème où une CASEexpression ne renvoie pas ce que j'attends.

En tant que test, j'ai ajouté une variable décimale et exécuté la même CASEexpression contre elle et cela fonctionne très bien, renvoyant les résultats comme je m'y attendais (arrondir la valeur quand IsGun=1. Mais quand je lance cette même CASEexpression contre une autre valeur décimale, elle renvoie toujours la valeur avec la CEILING()fonction et ne renvoie jamais la valeur d'origine.

Voici le code 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

Voici un extrait des résultats:

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

Et voici les données provenant 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

Comme vous pouvez le voir sur les cinq premiers, où IsGun = 0, la première CASEexpression utilisant la variable fixe renvoie la valeur UsingVar comme nous l'attendions, 12,54. Et pour les cinq derniers, il renvoie également la valeur attendue, 13.

Mais dans la deuxième CASEexpression (exactement la même logique), le PriceAdj utilise la CEILINGfonction sur chacun d'eux, que IsGun = 1 ou non.

Pourquoi la requête ne renvoie-t-elle pas les résultats attendus?

Dans certains des tableaux utilisés pour la vue d'ensemble, les types de données pour Price1 et Price2 étaient smallmoney et decimal (8,2) . Depuis, je les ai tous modifiés pour qu'ils soient décimaux (8,2) , mais cela n'a pas affecté les résultats.

Réponses:


11

Pour reproduire le problème:

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;

Ce qui se passe ici, c'est que CEILING(PQ.Price1Avg)produit un numeric(38, 0).

Selon la documentation , le type de sortie de CEILING()est du même type de données de base que l'entrée, bien que l'échelle (le nombre de décimales) puisse changer, ce qui se produit ici.

  • La AVG()fonction, dans mes tests, revient numeric(38, 6).
  • La CEILING()fonction de cette colonne, cependant, les sorties numeric(38, 0):

Vérifier:

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

Comme solution de contournement, vous pouvez convertir explicitement la sortie de la CEILING()fonction, ce qui devrait vous donner les résultats corrects:

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;

Il convient également de noter que les instructions case ne peuvent pas renvoyer plusieurs types différents, mais une expression IIF le peut, ce qui peut être conseillé.
Adam Martin

2
@AdamMartin qui n'est pas correct. Un iifest étendu à case. Il renvoie le type avec la plus grande priorité de type de données parmi les deux options. Aucune expression ne peut renvoyer le type de données X sur une ligne et le type de données Y sur une autre ligne pour la même colonne.
Martin Smith

@Daniel, le plus utile. Dès que j'ai CASTÉ (x COMME NUMÉRIQUE (8,2)) pour chaque sortie, il a renvoyé le résultat que je cherchais. Un grand merci à vous et à cette communauté!
Rodney G
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.