Выражение CASE возвращает неверное значение при использовании CEILING

11

Я столкнулся с проблемой, когда CASEвыражение не возвращает то, что я ожидаю.

В качестве теста я добавил десятичную переменную и применил к ней то же CASEвыражение, и оно работает нормально, возвращая результаты, как я и ожидал (округляя значение до, когда IsGun=1. Но когда я запускаю это же CASEвыражение против другого десятичного значения, оно всегда возвращает значение с CEILING()функцией и никогда не возвращает исходное значение.

Вот код 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

Вот фрагмент результатов:

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

А вот данные, полученные от 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

Как видно из первых пяти, где IsGun = 0, первое CASEвыражение, использующее фиксированную переменную, возвращает значение UsingVar, как мы и ожидали, 12.54. И для последних пяти он также возвращает ожидаемое значение 13.

Но во втором CASEвыражении (точно такая же логика) PriceAdj использует CEILINGфункцию для каждого из них, независимо от того, IsGun = 1 или нет.

Почему запрос не возвращает ожидаемые результаты?

В некоторых таблицах, используемых для представления объединения, типы данных для Price1 и Price2 были smallmoney и decimal (8,2) . С тех пор я изменил их все на десятичные (8,2) , но это не повлияло на результаты.

Родни Г
источник

Ответы:

11

Чтобы воспроизвести проблему:

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;

Здесь происходит то, что CEILING(PQ.Price1Avg)производит numeric(38, 0).

Согласно документации , тип выходных данных CEILING()имеет тот же базовый тип данных, что и входные данные, хотя масштаб (количество десятичных знаков) может измениться, что и происходит здесь.

  • AVG()Функция, в моих тестах, возвращается numeric(38, 6).
  • Однако CEILING()функция в этом столбце выдает numeric(38, 0):

Проверять:

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

В качестве обходного пути вы можете явно преобразовать выходные данные CEILING()функции, которые должны дать вам правильные результаты:

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;
Даниэль Хутмахер
источник
Следует также отметить, что операторы case не могут возвращать несколько различных типов, но может быть выражением IIF, что может быть целесообразным.
Адам Мартин
2
@ AdamMartin, это не правильно. iifПолучает расширяется к case. Она возвращает тип с наибольшим типом данных старшинства из двух вариантов. Для любого выражения невозможно вернуть тип данных X в одной строке и тип данных Y в другой строке для того же столбца.
Мартин Смит
@ Даниэль, самый полезный. Как только я произвел CAST (x AS NUMERIC (8,2)) для каждого вывода, он вернул искомый результат. Большое спасибо вам и этому сообществу!
Родни G