In che modo SQL Server determina precisione / scala?


8

Sto eseguendo SQL Server 2012

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

risultati:

 0.012500, 
 0.012480

Anche questo mi sta confondendo:

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 prima selezione mi dà il risultato corretto: 12644.44022 Il secondo tronca il risultato: 12644.00000


1
Vedere questa risposta: stackoverflow.com/a/51445919/87015 , contiene un codice di esempio che determina il tipo di dati previsto in base al tipo di dati di input e all'operazione.
Salman A

Risposte:


13

Determinare la precisione e la scala risultanti dalle espressioni è il nido di un topo e non credo che nessuno capisca le regole esatte in ogni scenario, specialmente quando si mescolano decimale (o float!) E int. Vedi questa risposta di gbn .

Ovviamente puoi personalizzare le espressioni per darti quello che vuoi facendo conversioni esplicite molto più dettagliate. Questo è probabilmente eccessivo ma:

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

Nessuno dei due risultati viene arrotondato in modo errato a causa della matematica in virgola mobile rotta o della precisione / scala selvaggiamente errate.

0.012500    0.012500

6

Come menzionato da Aaron Bertrand, le espressioni sono molto difficili da prevedere.

Se hai il coraggio di andare lì, potresti provare a ottenere alcune informazioni usando il seguente frammento:

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

Questo è il risultato:

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

(1 row(s) affected)

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

(1 row(s) affected)

3

Nonostante le eccellenti risposte già aggiunte a questa domanda, esiste un ordine di precedenza esplicitamente definito per la conversione dei tipi di dati in SQL Server.

Quando un operatore combina due espressioni di diversi tipi di dati, le regole per la precedenza del tipo di dati specificano che il tipo di dati con la precedenza inferiore viene convertito nel tipo di dati con la precedenza più alta. Se la conversione non è una conversione implicita supportata, viene restituito un errore. Quando entrambe le espressioni di operando hanno lo stesso tipo di dati, il risultato dell'operazione ha quel tipo di dati.

SQL Server utilizza il seguente ordine di precedenza per i tipi di dati:

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)

Così, per esempio, se si SELECT 0.5 * 1(moltiplicando un numero decimale da un int) si ottiene un risultato che viene convertito in un valore decimale, dal momento che decimalè più alta la precedenza rispetto al inttipo di dati.

Vedere http://msdn.microsoft.com/en-us/library/ms190309.aspx per ulteriori dettagli.

Detto questo, probabilmente SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); dovrebbe restituire un valore decimale, poiché praticamente tutti gli input sono decimali. È interessante notare che puoi forzare un risultato corretto modificandolo SELECTin:

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

Questo ritorna:

inserisci qui la descrizione dell'immagine

Sono in perdita per spiegare come ciò faccia la differenza, anche se chiaramente lo fa . La mia ipotesi è che 1E0(un float esplicito) nella POWER(funzione costringe SQL Server a fare una scelta diversa sui tipi di output per la POWERfunzione. Se la mia supposizione è corretta, ciò indicherebbe un possibile bug nella POWERfunzione, poiché la documentazione indica che il primo input in POWER()è un float o un numero che può essere implicitamente convertito in un float.


2
È inoltre appropriato un collegamento a Precisione, Scala e Lunghezza .
ypercubeᵀᴹ

2

Conosci la SELECT .. INTO sintassi ? È un trucco utile per decostruire situazioni come questa perché crea una tabella al volo con i giusti tipi di dati per l' SELECTelenco dato .

È possibile suddividere il calcolo nei suoi passaggi costitutivi, applicando le regole di precedenza dei server SQL man mano che si procede, per vedere come cambia la definizione. Ecco come apparirebbe il tuo primo esempio:

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;

Questo è l'output:

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

1
Questo non aiuta sempre però. Entrambi (1+1)e 2sono di tipo intma questa domanda ha un esempio in cui finiscono per produrre un risultato tipizzato in modo diverso.
Martin Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.