Wyrażenie CASE zwraca nieprawidłową wartość podczas korzystania z SUFITU

11

Natknąłem się na problem, w którym CASEwyrażenie nie zwraca tego, czego oczekuję.

W ramach testu dodałem zmienną dziesiętną i uruchomiłem CASEprzeciwko niej to samo wyrażenie i działa ono dobrze, zwracając wyniki zgodnie z oczekiwaniami (zaokrąglając wartość w górę, kiedy IsGun=1. Ale kiedy uruchamiam to samo CASEwyrażenie względem innej wartości dziesiętnej, zawsze zwraca wartość z CEILING()funkcją i nigdy nie zwraca oryginalnej wartości.

Oto kod 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

Oto fragment wyników:

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

A oto dane pochodzące z 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

Jak widać z pierwszych pięciu, gdzie IsGun = 0, pierwsze CASEwyrażenie wykorzystujące stałą zmienną zwraca wartość UsingVar, tak jak się spodziewalibyśmy, 12,54. A dla ostatnich pięciu zwraca również wartość, której moglibyśmy się spodziewać 13.

Ale w drugim CASEwyrażeniu (dokładnie ta sama logika) PriceAdj używa CEILINGfunkcji na każdym z nich, niezależnie od tego, czy IsGun = 1, czy nie.

Dlaczego zapytanie nie zwraca oczekiwanych wyników?

W niektórych stołach używanych do unii zobaczyć typy danych dla cena1 i price2 były smallmoney i decimal (8,2) . Od tego czasu zmieniłem je wszystkie na dziesiętne (8,2) , ale to nie wpłynęło na wyniki.

Rodney G.
źródło

Odpowiedzi:

11

Aby odtworzyć problem:

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;

To, co się tutaj dzieje, CEILING(PQ.Price1Avg)powoduje, że numeric(38, 0).

Zgodnie z dokumentacją typ wyjściowy CEILING()ma ten sam podstawowy typ danych co dane wejściowe, chociaż skala (liczba miejsc po przecinku) może się zmienić, co dzieje się tutaj.

  • AVG()Funkcja, w moich testach, powraca numeric(38, 6).
  • CEILING()Działanie w tej kolumnie, natomiast wyjścia numeric(38, 0):

Do weryfikacji:

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

Aby obejść ten problem, możesz jawnie przekonwertować dane wyjściowe CEILING()funkcji, co powinno dać poprawne wyniki:

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
źródło
Należy również zauważyć, że instrukcje case nie mogą zwracać wielu różnych typów, ale wyrażenie IIF może, więc może to być wskazane.
Adam Martin
2
@AdamMartin, które nie są poprawne. iifDostaje się rozszerzył case. To zwraca typ z największą pierwszeństwa typu danych z tych dwóch opcji. Żadne wyrażenie nie może zwrócić typu danych X w jednym wierszu i typu danych Y w innym wierszu dla tej samej kolumny.
Martin Smith
@Daniel, najbardziej pomocny. Gdy tylko I CAST (x AS NUMERIC (8,2)) dla każdego wyjścia zwrócił wynik, którego szukałem. Wielkie dzięki dla ciebie i tej społeczności!
Rodney G