¿Cómo determina SQL Server la precisión / escala?


8

Estoy ejecutando SQL Server 2012

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

Resultados:

 0.012500, 
 0.012480

Este es incluso más confuso para mí:

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

La primera selección me da el resultado correcto: 12644.44022 La segunda trunca el resultado: 12644.00000


1
Consulte esta respuesta: stackoverflow.com/a/51445919/87015 , contiene un código de muestra que determina el tipo de datos esperado en función del tipo de datos de entrada y la operación.
Salman A

Respuestas:


13

Determinar la precisión y la escala resultante de las expresiones es un nido de ratas y no creo que nadie entienda las reglas exactas en cada escenario, especialmente cuando se mezclan decimal (o flotante) e int. Ver esta respuesta de gbn .

Por supuesto, puede adaptar las expresiones para obtener lo que desea haciendo conversiones explícitas mucho más detalladas. Esto probablemente es exagerado pero:

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

Ninguno de los resultados se redondea incorrectamente debido a las matemáticas de coma flotante rotas o a la precisión / escala extremadamente incorrecta.

0.012500    0.012500

6

Como Aaron Bertrand mencionó, las expresiones son muy difíciles de predecir.

Si te atreves a ir allí, puedes intentar obtener una idea utilizando el siguiente fragmento:

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 es el resultado:

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

(1 row(s) affected)

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

(1 row(s) affected)

3

A pesar de las excelentes respuestas ya agregadas a esta pregunta, existe un orden de precedencia explícitamente definido para la conversión de tipos de datos en SQL Server.

Cuando un operador combina dos expresiones de diferentes tipos de datos, las reglas para la precedencia del tipo de datos especifican que el tipo de datos con la precedencia más baja se convierte al tipo de datos con la precedencia más alta. Si la conversión no es una conversión implícita compatible, se devuelve un error. Cuando ambas expresiones de operando tienen el mismo tipo de datos, el resultado de la operación tiene ese tipo de datos.

SQL Server usa el siguiente orden de precedencia para los tipos de datos:

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)

Entonces, por ejemplo, si usted SELECT 0.5 * 1(multiplicando un decimal por un int) obtiene un resultado que se convierte en un valor decimal, ya que decimaltiene mayor prioridad que el inttipo de datos.

Consulte http://msdn.microsoft.com/en-us/library/ms190309.aspx para obtener más detalles.

Habiendo dicho todo eso, probablemente SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); debería devolver un valor decimal, ya que prácticamente todas las entradas son decimales. Curiosamente, puede forzar un resultado correcto modificando eso 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));

Esto devuelve:

ingrese la descripción de la imagen aquí

No puedo explicar cómo eso hace alguna diferencia, aunque claramente lo hace . Mi conjetura es el 1E0(un flotador explícita) en las POWER(fuerzas de función de SQL Server para tomar una decisión diferente en los tipos de salida para la POWERfunción. Si mi suposición es correcta, eso indicaría un posible error en la POWERfunción, ya que la documentación indica que la primera entrada POWER()es un flotante, o un número que puede convertirse implícitamente en un flotante.


2
Un enlace a Precisión, Escala y Longitud también es apropiado.
ypercubeᵀᴹ

2

¿Estás familiarizado con la SELECT .. INTO sintaxis ? Es un truco útil para deconstruir situaciones como esta porque crea una tabla sobre la marcha con los tipos de datos correctos para la SELECTlista dada .

Puede dividir su cálculo en sus pasos constitutivos, aplicando las reglas de precedencia de los servidores SQL a medida que avanza, para ver cómo cambia la definición. Así es como se vería su primer ejemplo:

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 es la salida:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6

1
Sin embargo, esto no siempre ayuda. Ambos (1+1)y 2son de tipo, intpero esta pregunta tiene un ejemplo en el que terminan produciendo un resultado de tipo diferente.
Martin Smith
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.