SELECT vs UPDATE, неожиданное округление при использовании функции ABS - PullRequest
0 голосов
/ 07 ноября 2018

В приложении приведен пример кода для запуска в SQL. Это выглядит как неожиданное поведение для SQL Server. Что должно произойти, это удалить отрицательный знак из числа, но при использовании той же функции в команде обновления он получает абсолютное значение, а также округляет число. Почему это?

DECLARE @TEST TABLE (TEST varchar(2048));

INSERT INTO @TEST VALUES ('  -29972.95');

SELECT TEST FROM @TEST;

SELECT ABS(TEST) FROM @TEST;

UPDATE @TEST SET TEST = ABS(TEST);

SELECT TEST FROM @TEST;

Ниже приведены результаты этого кода.

  -29972.95
29972.95
29973

Ответы [ 4 ]

0 голосов
/ 08 ноября 2018

Это больше похоже на «функцию» функции CONVERT, чем что-либо, имеющее отношение к SELECT или UPDATE (единственная причина, по которой она отличается, заключается в том, что UPDATE неявно преобразует FLOAT(8), возвращаемое ABS(...) обратно в VARCHAR).

Вычислительный скаляр в плане обновления содержит выражение

[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(varchar(2048),
                                              abs(CONVERT_IMPLICIT(float(53),[TEST],0))
                                              ,0) /*<-- style used for convert from float*/
                            )

Значение - Выход

0 (по умолчанию) - максимум 6 цифр. При необходимости используйте в научной записи.

1 - всегда 8 цифр. Всегда используйте в научной записи.

2 - всегда 16 цифр. Всегда используйте в научной записи.

Из MSDN: https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-2017

Это можно увидеть в следующем примере:

SELECT
    [# Digits],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N)) AS [FLOAT(VARCHAR(N))],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N, 0)) AS [FLOAT(VARCHAR(N, 0))],
    CONVERT(FLOAT(8), CONVERT(VARCHAR(20), N, 1)) AS [FLOAT(VARCHAR(N, 1))]
FROM (SELECT '6 digits', ABS('9972.95') UNION ALL SELECT '7 digits', ABS('29972.95')) T ([# Digits], N)

Возвращает следующие результаты:

# Digits FLOAT(VARCHAR(N)) FLOAT(VARCHAR(N, 0)) FLOAT(VARCHAR(N, 1))
-------- ----------------- -------------------- --------------------
6 digits 9972.95           9972.95              9972.95
7 digits 29973             29973                29972.95

Это доказывает, что UPDATE эффективно использовал CONVERT(VARCHAR, ABS(...)) со стилем по умолчанию "0". Это ограничило FLOAT от ABS до 6 цифр. Убирая 1 символ, чтобы он не переполнял неявное преобразование, в этом сценарии сохраняются фактические значения.

Возвращаем это к OP:

  • Функция ABS в этом случае возвращает FLOAT(8) в примере.
  • Затем UPDATE вызвал неявное преобразование, которое было эффективно `CONVERT (VARCHAR (2048), ABS (...), 0), которое затем переполнило максимальные цифры стиля по умолчанию.
  • Чтобы обойти это поведение (если это связано с практической проблемой), вам нужно указать стиль 1 или 2 (или даже 3, чтобы получить 17 цифр), чтобы избежать этого усечения (но обязательно обращайтесь к научным обозначение используется, поскольку теперь оно всегда возвращается в этом случае)
0 голосов
/ 07 ноября 2018

(некоторые предварительные тесты для краткости исключены)

Это определенно связано с тихим усечением во время INSERT / UPDATE.

Если вы измените вставку значения на это:

INSERT INTO @TEST SELECT ABS('  -29972.95')

Вы сразу получаете такое же округление / усечение, не делая ОБНОВЛЕНИЕ.

Между тем, SELECT ABS(' -29972.95') дает ожидаемые результаты.

Дальнейшее тестирование поддерживает теорию неявного преобразования с плавающей запятой и указывает, что виновником является обратное преобразование в varchar:

DECLARE @Flt float = '  -29972.95'

SELECT @Flt;

SELECT CAST(@Flt AS varchar(2048))

Производит:

-29972.95

-29972

Возможно, окончательное редактирование:

Я нюхал то же дерево, что и Мартин. Я нашел это .

Что заставило меня попробовать это:

DECLARE @Flt float = '  -29972.95'

SELECT @Flt;

SELECT CONVERT(varchar(2048),@Flt,128)

Который произвел это:

-29972.95

-29972.95

Так что я назову это немного документированным , поскольку стиль 128 - это устаревший стиль, который устарел и может исчезнуть в будущем выпуске. Но ни один из ныне документированных стилей не дает такого же результата. Очень интересно.

0 голосов
/ 07 ноября 2018

ABS() должен работать с числовыми значениями, а ввод varchar преобразуется в число с плавающей точкой. Наиболее вероятным объяснением этого поведения является то, что float имеет наивысший приоритет среди всех числовых типов данных, таких как decimal, int, bit.

Ваш оператор SELECT просто возвращает результат с плавающей точкой. Однако оператор UPDATE неявно преобразует число с плавающей запятой обратно в varchar, что приводит к неожиданным результатам:

SELECT
    test,
    ABS(test) AS test_abs,
    CAST(ABS(test) AS VARCHAR(100)) AS test_abs_str
FROM (VALUES
    ('-29972.95'),
    ('-29972.94'),
    ('-29972.9')
) AS test(test)

test      | test_abs | test_abs_str
----------|----------|-------------
-29972.95 | 29972.95 | 29973
-29972.94 | 29972.94 | 29972.9
-29972.9  |  29972.9 | 29972.9

Я бы предложил использовать явное преобразование и точный числовой тип данных, чтобы избежать этой и других потенциальных проблем с неявными преобразованиями / числами с плавающей запятой:

SELECT
    test,
    ABS(CAST(test AS DECIMAL(18, 2))) AS test_abs,
    CAST(ABS(CAST(test AS DECIMAL(18, 2))) AS VARCHAR(100)) AS test_abs_str
FROM (VALUES
    ('-29972.95'),
    ('-29972.94'),
    ('-29972.9')
) AS test(test)

test      | test_abs | test_abs_str
----------|----------|-------------
-29972.95 | 29972.95 | 29972.95
-29972.94 | 29972.94 | 29972.94
-29972.9  | 29972.90 | 29972.90
0 голосов
/ 07 ноября 2018

ABS - это математическая функция, то есть она предназначена для работы с числовыми значениями, вы не можете ожидать правильного поведения функции при использовании других типов данных, как в этом случае VARCHAR, я предлагаю сначала выполнить требуемый CAST для числового значения Тип данных перед применением функции ABS выглядит следующим образом:

UPDATE @TEST SET TEST = ABS(CAST(TEST AS DECIMAL(18,2)))

После этого ваш запрос выведет

29972,95

Это не решает, насколько возможно, что ABS работает нормально при выборе, а не при обновлении значения, возможно, это ошибка в sqlserver, но также очень плохая практика - избегать приведения к нужным типам данных, требуемым функциями. Возможно, неявное приведение происходит, когда выполняется предложение SELECT, но игнорируется в UPDATE, потому что Microsoft ожидает, что вы поступите правильно.

...