Как SQL Server определяет точность / масштаб?

8

Я использую SQL Server 2012

SELECT 
   0.15 * 30 / 360,
   0.15 / 360 * 30 

Результаты:

 0.012500, 
 0.012480

Это даже сбивает меня с толку:

DECLARE @N INT = 360
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360     
DECLARE @C DECIMAL(38,26) = 1000000     

SELECT @C *  @I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 )
SELECT @C * (@I *  POWER(1 + @I, @N)  / ( POWER(1 + @I, @N) - 1 ) )

Первый выбор дает мне правильный результат: 12644.44022 Второй выбор усекает результат: 12644.00000

cpacheco
источник
1
Смотрите этот ответ: stackoverflow.com/a/51445919/87015 , он содержит пример кода, который определяет ожидаемый тип данных на основе типа входных данных и операции.
Салман А

Ответы:

13

Определение точности и масштаба в результате выражений - это крысиное гнездо, и я не думаю, что кто-то понимает точные правила в каждом сценарии, особенно при смешивании десятичных (или плавающих!) И целых чисел. Смотрите этот ответ от gbn .

Вы можете, конечно, адаптировать выражения, чтобы дать вам то, что вы хотите, делая гораздо более подробные явные преобразования. Это, вероятно, излишне, но:

SELECT 
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   * CONVERT(DECIMAL(15,6), 30) 
   / CONVERT(DECIMAL(15,6), 360)),
   CONVERT(DECIMAL(15,6), CONVERT(DECIMAL(15,6), 0.15) 
   / CONVERT(DECIMAL(15,6), 360) 
   * CONVERT(DECIMAL(15,6), 30));

Ни один из результатов не округляется неправильно из-за неработающей математики с плавающей запятой или дико неверной точности / шкалы.

0.012500    0.012500
Аарон Бертран
источник
6

Как отметил Аарон Бертран, выражения очень сложно предсказать.

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

DECLARE @number SQL_VARIANT
SELECT @number = 0.15 / 360
SELECT @number
SELECT  
    SQL_VARIANT_PROPERTY(@number, 'BaseType') BaseType,
    SQL_VARIANT_PROPERTY(@number, 'MaxLength') MaxLength,
    SQL_VARIANT_PROPERTY(@number, 'Precision') Precision

Это результат:

------------
0.000416

(1 row(s) affected)

BaseType     MaxLength    Precision
------------ ------------ ----------
numeric      5            6

(1 row(s) affected)
Оливер Ранер
источник
3

Несмотря на прекрасные ответы, уже добавленные к этому вопросу, существует явно определенный порядок приоритета для преобразования типов данных в SQL Server.

Когда оператор объединяет два выражения разных типов данных, правила для приоритета типа данных указывают, что тип данных с более низким приоритетом преобразуется в тип данных с более высоким приоритетом. Если преобразование не поддерживается неявным преобразованием, возвращается ошибка. Если оба выражения операнда имеют один и тот же тип данных, результат операции имеет этот тип данных.

SQL Server использует следующий порядок приоритета для типов данных:

user-defined data types (highest)
sql_variant
xml
datetimeoffset
datetime2
datetime
smalldatetime
date
time
float
real
decimal
money
smallmoney
bigint
int
smallint
tinyint
bit
ntext
text
image
timestamp
uniqueidentifier
nvarchar (including nvarchar(max) )
nchar
varchar (including varchar(max) )
char
varbinary (including varbinary(max) )
binary (lowest)

Так, например, если вы SELECT 0.5 * 1(умножая десятичное число на целое число) вы получите результат, который преобразуется в десятичное значение, так decimalкак приоритет выше, чем у intтипа данных.

См. Http://msdn.microsoft.com/en-us/library/ms190309.aspx для получения дополнительной информации.

Сказав все это, вероятно , SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); должно вернуть десятичное значение, так как практически все входные данные являются десятичными. Интересно, что вы можете принудительно изменить результат, изменив его SELECTна:

DECLARE @N INT = 360;
DECLARE @I DECIMAL(38,26) = 0.15 * 30 / 360;
DECLARE @C DECIMAL(38,26) = 1000000;

SELECT @C *  @I *  POWER(1 + @I, @N)  / (POWER(1 + @I, @N) - 1);
SELECT @C * (@I *  POWER(1 + @I, @N)  / (POWER(1E0 + @I, @N) - 1));

Это возвращает:

введите описание изображения здесь

Я затрудняюсь объяснить, как это имеет какое-то значение, хотя, очевидно, это имеет значение . Я предполагаю , что 1E0(явное значение с плавающей точкой) в POWER(функции заставляет SQL Server сделать другой выбор типов вывода для POWERфункции. Если мое предположение верно, это указывало бы на возможную ошибку в POWERфункции, поскольку в документации указано, что первым вводом POWER()является число с плавающей точкой или число, которое можно неявно преобразовать в число с плавающей точкой.

Макс Вернон
источник
2
Ссылка на точность, масштаб и длину также подходит.
ypercubeᵀᴹ
2

Вы знакомы с SELECT .. INTO синтаксисом ? Это полезный трюк для деконструкции подобных ситуаций, потому что он создает на лету таблицу с правильными типами данных для данного SELECTсписка.

Вы можете разбить свой расчет на составляющие его этапы, применяя правила приоритета SQL Server по мере необходимости, чтобы увидеть, как меняется определение. Вот как будет выглядеть ваш первый пример:

use tempdb;

SELECT
   0.15             as a,
   0.15 * 30        as b,
   0.15 * 30 / 360  as c
into #Step1;

select * from #Step1;

select
    c.name,
    t.name,
    c.precision,
    c.scale
from sys.columns as c
inner join sys.types as t
    on t.system_type_id = c.system_type_id
where object_id = object_id('#Step1');

drop table #Step1;

Это вывод:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6
Майкл Грин
источник
1
Это не всегда помогает, хотя. Оба (1+1)и 2имеют тип, intно у этого вопроса есть пример, где они заканчивают тем, что приводили к другому типу результата.
Мартин Смит