Wie bestimmt SQL Server die Genauigkeit / Skalierung?

8

Ich verwende SQL Server 2012

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

Ergebnisse:

 0.012500, 
 0.012480

Dieser ist für mich noch verwirrender:

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

Die erste Auswahl gibt mir das richtige Ergebnis: 12644.44022 Die zweite Auswahl schneidet das Ergebnis ab: 12644.00000

cpacheco
quelle
1
Siehe diese Antwort: stackoverflow.com/a/51445919/87015 , enthält einen Beispielcode, der den erwarteten Datentyp basierend auf dem Eingabedatentyp und der Operation bestimmt.
Salman A

Antworten:

13

Die Bestimmung der Genauigkeit und Skalierung, die sich aus Ausdrücken ergibt, ist ein Rattennest, und ich glaube, niemand versteht die genauen Regeln in jedem Szenario, insbesondere beim Mischen von Dezimal (oder Float!) Und Int. Siehe diese Antwort von gbn .

Sie können die Ausdrücke natürlich so anpassen, dass sie Ihnen das bieten, was Sie möchten, indem Sie viel ausführlichere explizite Konvertierungen vornehmen. Dies ist wahrscheinlich übertrieben, aber:

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

Keines der Ergebnisse wird aufgrund einer gebrochenen Gleitkomma-Mathematik oder einer völlig falschen Genauigkeit / Skalierung falsch gerundet.

0.012500    0.012500
Aaron Bertrand
quelle
6

Wie Aaron Bertrand erwähnte, sind Ausdrücke sehr schwierig vorherzusagen.

Wenn Sie es wagen, dorthin zu gehen, können Sie versuchen, mithilfe des folgenden Ausschnitts einen Einblick zu gewinnen:

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

Das ist das Ergebnis:

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

(1 row(s) affected)

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

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

Ungeachtet der hervorragenden Antworten, die dieser Frage bereits hinzugefügt wurden, gibt es eine explizit definierte Rangfolge für die Konvertierung von Datentypen in SQL Server.

Wenn ein Operator zwei Ausdrücke unterschiedlicher Datentypen kombiniert, geben die Regeln für die Priorität des Datentyps an, dass der Datentyp mit der niedrigeren Priorität in den Datentyp mit der höheren Priorität konvertiert wird. Wenn die Konvertierung keine unterstützte implizite Konvertierung ist, wird ein Fehler zurückgegeben. Wenn beide Operandenausdrücke denselben Datentyp haben, hat das Ergebnis der Operation diesen Datentyp.

SQL Server verwendet die folgende Rangfolge für Datentypen:

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)

Wenn Sie beispielsweise SELECT 0.5 * 1eine Dezimalstelle mit einem Int multiplizieren, erhalten Sie ein Ergebnis, das in einen Dezimalwert konvertiert wird, da decimales eine höhere Priorität als der intDatentyp hat.

Weitere Informationen finden Sie unter http://msdn.microsoft.com/en-us/library/ms190309.aspx .

Trotzdem SELECT @C * (@I * POWER(1 + @I, @N) / (POWER(1 + @I, @N) - 1 )); sollte wahrscheinlich ein Dezimalwert zurückgegeben werden, da praktisch alle Eingaben dezimal sind. Interessanterweise können Sie ein korrektes Ergebnis erzwingen, indem Sie Folgendes ändern 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));

Dies gibt zurück:

Geben Sie hier die Bildbeschreibung ein

Ich kann nicht erklären, wie das einen Unterschied macht, obwohl dies eindeutig der Fall ist . Ich vermute, dass der 1E0(explizite Gleitkommawert) in der POWER(Funktion SQL Server dazu zwingt, eine andere Auswahl für die Ausgabetypen für die POWERFunktion zu treffen . Wenn meine Vermutung richtig ist, würde dies auf einen möglichen Fehler in der POWERFunktion hinweisen , da in der Dokumentation angegeben ist, dass die erste Eingabe POWER()ein Float oder eine Zahl ist, die implizit in einen Float konvertiert werden kann.

Max Vernon
quelle
2
Ein Link zu Präzision, Skalierung und Länge ist ebenfalls angemessen.
Ypercubeᵀᴹ
2

Kennen Sie die SELECT .. INTO Syntax ? Dies ist ein nützlicher Trick, um Situationen wie diese zu dekonstruieren, da im laufenden Betrieb eine Tabelle mit genau den richtigen Datentypen für die angegebene SELECTListe erstellt wird.

Sie können Ihre Berechnung in ihre einzelnen Schritte aufteilen und dabei die Vorrangregeln von SQL Servern anwenden, um zu sehen, wie sich die Definition ändert. So würde Ihr erstes Beispiel aussehen:

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;

Dies ist die Ausgabe:

name    name        precision   scale
a       numeric     2           2
b       numeric     5           2
c       numeric     9           6
Michael Green
quelle
1
Dies hilft jedoch nicht immer. Beide (1+1)und 2sind vom Typ, intaber diese Frage hat ein Beispiel, bei dem sie am Ende ein anders typisiertes Ergebnis liefern.
Martin Smith