Der CASE-Ausdruck gibt bei Verwendung von CEILING einen falschen Wert zurück

11

Ich bin auf ein Problem gestoßen, bei dem ein CASEAusdruck nicht das zurückgibt, was ich erwarte.

Als Test habe ich eine Dezimalvariable hinzugefügt und denselben CASEAusdruck ausgeführt, und es funktioniert einwandfrei. Die Ergebnisse werden wie erwartet zurückgegeben (Aufrunden des Werts, wenn IsGun=1. Wenn ich denselben CASEAusdruck jedoch mit einem anderen Dezimalwert ausführe , wird immer zurückgegeben den Wert mit der CEILING()Funktion und gibt niemals den ursprünglichen Wert zurück.

Hier ist der SQL-Code:

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

Hier ist ein Ausschnitt der Ergebnisse:

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

Und hier sind die Daten, die von vProducts_PriceQty_Union stammen :

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

Wie Sie aus den ersten fünf sehen können, wobei IsGun = 0 ist, gibt der erste CASEAusdruck, der die feste Variable verwendet, den UsingVar- Wert wie erwartet zurück, 12.54. Und für die letzten fünf gibt es auch den Wert zurück, den wir erwarten würden, 13.

Im zweiten CASEAusdruck (genau dieselbe Logik) verwendet der PriceAdj die CEILINGFunktion für jeden einzelnen von ihnen, unabhängig davon, ob IsGun = 1 ist oder nicht.

Warum gibt die Abfrage nicht die erwarteten Ergebnisse zurück?

In einigen der für die Union verwendeten Tabellen sind die Datentypen für sehen Preis1 und Preis2 waren smallmoney- und dezimal (8,2) . Ich habe sie seitdem alle auf Dezimalzahl (8,2) geändert , aber das hatte keinen Einfluss auf die Ergebnisse.

Rodney G.
quelle

Antworten:

11

So reproduzieren Sie das 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;

Was hier passiert ist, dass CEILING(PQ.Price1Avg)ein numeric(38, 0).

Gemäß der Dokumentation hat der Ausgabetyp von CEILING()demselben Basisdatentyp wie die Eingabe, obwohl sich die Skalierung (die Anzahl der Dezimalstellen) ändern kann, was hier passiert.

  • Die AVG()Funktion kehrt in meinen Tests zurück numeric(38, 6).
  • Die CEILING()Funktion in dieser Spalte gibt jedoch Folgendes aus numeric(38, 0):

Verifizieren:

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

CEILING()Um dieses Problem zu umgehen, können Sie die Ausgabe der Funktion explizit konvertieren , um die richtigen Ergebnisse zu erhalten:

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
quelle
Sollte auch beachten, dass case-Anweisungen nicht mehrere verschiedene Typen zurückgeben können, ein IIF-Ausdruck jedoch, so dass dies ratsam sein kann.
Adam Martin
2
@ AdamMartin das ist nicht richtig. Ein iifwird erweitert zu case. Es gibt den Typ mit der höchsten Datentyppriorität aus den beiden Optionen zurück. Es ist nicht möglich, dass ein Ausdruck den Datentyp X in einer Zeile und den Datentyp Y in einer anderen Zeile für dieselbe Spalte zurückgibt.
Martin Smith
@ Daniel, sehr hilfreich. Sobald ich für jede Ausgabe CAST (x AS NUMERIC (8,2)) habe, wurde das gesuchte Ergebnis zurückgegeben. Vielen Dank an dich und diese Community!
Rodney G