Como o SQL Server determina precisão / escala?

8

Estou executando o SQL Server 2012

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

Resultados:

 0.012500, 
 0.012480

Este é até mais confuso para mim:

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 ) )

A primeira seleção fornece o resultado correto: 12644.44022 A segunda trunca o resultado: 12644.00000

cpacheco
fonte
11
Consulte esta resposta: stackoverflow.com/a/51445919/87015 , contém um código de amostra que determina o tipo de dados esperado com base no tipo e operação de dados de entrada.
Salman A

Respostas:

13

Determinar a precisão e a escala resultantes das expressões é um ninho de ratos e eu não acho que alguém entenda as regras exatas em todos os cenários, especialmente ao misturar decimal (ou float!) E int. Veja esta resposta por gbn .

Obviamente, você pode adaptar as expressões para fornecer o que deseja, fazendo conversões explícitas muito mais detalhadas. Provavelmente é um exagero, mas:

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));

Nenhum dos resultados é arredondado incorretamente devido à matemática de ponto flutuante quebrada ou à precisão / escala totalmente erradas.

0.012500    0.012500
Aaron Bertrand
fonte
6

Como Aaron Bertrand mencionou, as expressões são muito difíceis de prever.

Se você se atreve a ir para lá, pode tentar obter algumas dicas usando o seguinte snippet:

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

Este é o resultado:

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

(1 row(s) affected)

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

(1 row(s) affected)
Oliver Rahner
fonte
3

Não obstante as excelentes respostas já adicionadas a esta pergunta, há uma ordem de precedência explicitamente definida para a conversão de tipos de dados no SQL Server.

Quando um operador combina duas expressões de tipos de dados diferentes, as regras para a precedência do tipo de dados especificam que o tipo de dados com a precedência mais baixa é convertido no tipo de dados com a precedência mais alta. Se a conversão não for uma conversão implícita suportada, um erro será retornado. Quando ambas as expressões de operando têm o mesmo tipo de dados, o resultado da operação possui esse tipo de dados.

O SQL Server usa a seguinte ordem de precedência para tipos de dados:

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)

Assim, por exemplo, se você SELECT 0.5 * 1(multiplicando um decimal por um int), obtém um resultado que é convertido em um valor decimal, pois decimalé uma precedência mais alta que o inttipo de dados.

Consulte http://msdn.microsoft.com/en-us/library/ms190309.aspx para obter mais detalhes.

Dito tudo isso, provavelmente SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); deve retornar um valor decimal, pois praticamente todas as entradas são decimais. Curiosamente, você pode forçar um resultado correto modificando isso SELECTpara:

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));

Isso retorna:

insira a descrição da imagem aqui

Não consigo explicar como isso faz alguma diferença, embora claramente faça . Meu palpite é que 1E0(uma flutuação explícita) na POWER(função força o SQL Server a fazer uma escolha diferente nos tipos de saída para a POWERfunção. Se minha suposição estiver correta, isso indicaria um possível bug na POWERfunção, uma vez que a documentação indica que a primeira entrada POWER()é float ou um número que pode ser implicitamente convertido em float.

Max Vernon
fonte
2
Um link para Precisão, Escala e Comprimento também é apropriado.
precisa saber é o seguinte
2

Você está familiarizado com a SELECT .. INTO sintaxe ? É um truque útil para desconstruir situações como essa, porque cria uma tabela em tempo real com apenas os tipos de dados corretos para a SELECTlista fornecida .

Você pode dividir seu cálculo em suas etapas constituintes, aplicando as regras de precedência dos SQL Servers à medida que avança, para ver como a definição é alterada. Aqui está como seu primeiro exemplo seria:

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;

Esta é a saída:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6
Michael Green
fonte
11
Isso nem sempre ajuda. Ambos (1+1)e 2são do tipo, intmas esta questão tem um exemplo em que eles acabam produzindo um resultado de tipo diferente.
Martin Smith