Spanner bietet den Typ NUMERIC
, mit dem Zahlen mit Dezimalstellen genau gespeichert werden können.
Die Semantik des Typs NUMERIC
in Spanner variiert zwischen den beiden SQL-
(GoogleSQL und PostgreSQL), insbesondere im Zusammenhang mit der
Limits für Skalierung und Genauigkeit:
NUMERIC
im PostgreSQL-Dialekt ist ein numerischer Typ mit beliebiger Dezimalgenauigkeit (Skalierung oder Genauigkeit kann eine beliebige Zahl innerhalb des unterstützten Bereichs sein) und eignet sich daher ideal zum Speichern numerischer Daten mit beliebiger Genauigkeit.NUMERIC
in GoogleSQL ist ein numerischer Datentyp mit fester Genauigkeit (Genauigkeit=38 und Skalierung=9) und kann nicht zum Speichern von numerischen Daten mit beliebiger Genauigkeit verwendet werden. Wenn Sie in GoogleSQL-Datenbanken Zahlen mit beliebiger Genauigkeit speichern möchten, empfehlen wir, sie als Strings zu speichern.
Genauigkeit numerischer Spanner-Typen
Genauigkeit ist die Anzahl der Ziffern in einer Zahl. Skalieren ist die Anzahl der Ziffern rechts vom Dezimalzeichen in einer Zahl. Die Zahl 123,456 hat beispielsweise eine Genauigkeit von 6 und eine Skala von 3. Spanner hat drei numerische Typen:
- Vorzeichenbehafteter 64-Bit-Ganzzahltyp mit dem Namen
INT64
im GoogleSQL-Dialekt undINT8
im PostgreSQL-Dialekt. - IEEE-64-Bit-Gleitkommatyp (doppelt) mit binärer Genauigkeit, im GoogleSQL-Dialekt
FLOAT64
und im PostgreSQL-DialektFLOAT8
genannt. - Dezimalstellen
NUMERIC
Sehen wir uns die Genauigkeit und Skalierung an.
INT64
/ INT8
stellt numerische Werte dar, die keine Bruchkomponente haben. Dieser Datentyp bietet eine Genauigkeit von 18 Stellen mit einer Skala von null.
FLOAT64
/ FLOAT8
kann nur ungefähre dezimale numerische Werte mit Bruchkomponenten darstellen und bietet
15 bis 17 signifikante Ziffern (Anzahl der Ziffern einer Zahl mit allen nachgestellten Nullen)
dezimale Genauigkeit entfernt werden. Dieser Typ stellt ungefähre Dezimalzahlen dar, da die von Spanner verwendete IEEE-64-Bit-Gleitkomma-Binärdarstellung keine exakten Dezimalbrüche (Basis 10) darstellen kann. Nur Brüche mit Basis 2 können genau dargestellt werden.
Dieser Genauigkeitsverlust führt bei einigen Dezimalbrüchen zu Rundungsfehlern.
Beispiel: Wenn Sie den Dezimalwert 0, 2 mit FLOAT64
/ FLOAT8
speichern
Datentyp, wird die Binärdarstellung wieder in den Dezimalwert von
0,20000000000000001 (mit einer Genauigkeit von 18 Stellen) Auf ähnliche Weise wird (1,4 * 165) wieder in 230,999999999999971 und (0,1 + 0,2) wieder in 0,30000000000000004 konvertiert. Aus diesem Grund gelten für 64-Bit-Gleitkommazahlen
signifikante Stellen (nur einige Zahlen mit mehr als 15 Dezimalstellen)
Ziffern als 64-Bit-Gleitkommazahl ohne Rundung dargestellt werden. Weitere Informationen zu
siehe Gleitkommazahl mit doppelter Genauigkeit
Format.
Weder INT64
/ INT8
noch FLOAT64
/ FLOAT8
hat die ideale Genauigkeit für
Finanzielle, wissenschaftliche oder technische Berechnungen, bei denen eine Genauigkeit von 30
Ziffern oder mehr sind in der Regel erforderlich.
Der Datentyp NUMERIC
eignet sich für diese Anwendungen, da er exakte numerische Werte mit einer Dezimalgenauigkeit von mehr als 30 Dezimalstellen darstellen kann.
GoogleSQL-Daten aus NUMERIC
-Typ kann Zahlen mit einer festen Dezimalgenauigkeit von 38 und einer festen Skala darstellen.
von 9. Der Bereich von GoogleSQL NUMERIC
ist -99999999999999999999999999999.999999999 bis 99999999999999999999999999999.999999999.
Der PostgreSQL-Dialekt NUMERIC
kann Zahlen mit einem
maximale Dezimalgenauigkeit von 147.455 und eine maximale Skala von 16.383.
Wenn Sie Zahlen speichern müssen, die größer als die Genauigkeit und Skalierung sind, die von NUMERIC
angeboten werden, werden in den folgenden Abschnitten einige empfohlene Lösungen beschrieben.
Empfehlung: Beliebige Zahlen als Strings speichern
Wenn Sie eine Zahl mit beliebiger Genauigkeit in einer Spanner-Datenbank speichern und mehr Genauigkeit benötigen als NUMERIC
bereitstellt, empfehlen wir, den Wert als Dezimalwert in einer STRING
-/VARCHAR
-Spalte zu speichern. Beispielsweise wird die Zahl 123.4
als String "123.4"
gespeichert.
Bei diesem Ansatz muss Ihre Anwendung eine verlustfreie Konvertierung zwischen
Die anwendungsinterne Darstellung der Nummer und von STRING
/ VARCHAR
Spaltenwert für Lese- und Schreibvorgänge von Datenbanken.
Die meisten Bibliotheken mit beliebiger Genauigkeit haben für die Durchführung dieser verlustfreien Konvertierung integrierte Methoden. In Java können Sie beispielsweise die Methode BigDecimal.toPlainString()
und den Konstruktor BigDecimal(String)
verwenden.
Das Speichern der Zahl als String hat den Vorteil, dass der Wert mit
genaue Genauigkeit (bis zur Spaltenlängenbeschränkung STRING
/ VARCHAR
) und der Wert
und bleibt visuell lesbar.
Exakte Aggregationen und Berechnungen durchführen
Es ist erforderlich, dass die Anwendung die folgenden Berechnungen durchführt, damit exakte Aggregationen und Berechnungen für die Darstellung von Zeichenfolgen für Zahlen mit beliebiger Genauigkeit ausgeführt werden können. Sie können keine SQL-Aggregatfunktionen verwenden.
Wenn Sie beispielsweise das Äquivalent eines SQL-SUM(value)
über einen Zeilenbereich ausführen möchten, muss die Anwendung die Stringwerte für die Zeilen abfragen, dann konvertieren und intern in der Anwendung addieren.
Ungefähre Aggregationen, Sortierungen und Berechnungen durchführen
Mit SQL-Abfragen können Sie ungefähre Berechnungen von aggregierten Daten durchführen, indem Sie
Umwandlung der Werte in FLOAT64
/ FLOAT8
.
GoogleSQL
SELECT SUM(CAST(value AS FLOAT64)) FROM my_table
PostgreSQL
SELECT SUM(value::FLOAT8) FROM my_table
Ebenso können Sie durch den Umwandlungsvorgang Werte nach numerischen Werten oder Grenzwerte nach Bereich sortieren:
GoogleSQL
SELECT value FROM my_table ORDER BY CAST(value AS FLOAT64);
SELECT value FROM my_table WHERE CAST(value AS FLOAT64) > 100.0;
PostgreSQL
SELECT value FROM my_table ORDER BY value::FLOAT8;
SELECT value FROM my_table WHERE value::FLOAT8 > 100.0;
Diese Berechnungen entsprechen ungefähr den Grenzwerten des Datentyps FLOAT64
/ FLOAT8
.
Alternativen
Es gibt noch andere Möglichkeiten für die Speicherung von Zahlen mit beliebiger Genauigkeit in Spanner. Wenn das Speichern von Zahlen mit beliebiger Genauigkeit als Strings für Ihre Anwendung nicht funktioniert, sollten Sie die folgenden Alternativen in Betracht ziehen:
Anwendungsskalierte Ganzzahlwerte speichern
Sie können Zahlen mit beliebiger Genauigkeit speichern. Skalieren Sie dazu die Werte vor dem Schreiben, sodass Zahlen immer als Ganzzahlen gespeichert und die Werte nach dem Lesen noch einmal skaliert werden. Ihre Anwendung speichert einen festen Skalierungsfaktor und die Genauigkeit
ist auf die 18 Ziffern beschränkt, die durch den Datentyp INT64
/ INT8
bereitgestellt werden.
Nehmen Sie beispielsweise eine Zahl, die mit einer Genauigkeit von 5 Dezimalstellen gespeichert werden muss. Die Anwendung konvertiert den Wert durch Multiplikation mit 100.000 in eine Ganzzahl (Verschiebung des Dezimalpunkts um 5 Stellen nach rechts), sodass der Wert 12,54321 als 1254321
gespeichert wird.
In monetärer Hinsicht ist dieser Ansatz vergleichbar mit dem Speichern von Dollarwerten als das Vielfache von Millicents, ähnlich wie das Speichern von Zeiteinheiten in Millisekunden.
Die Anwendung bestimmt den festen Skalierungsfaktor. Wenn Sie den Skalierungsfaktor ändern, ist es erforderlich, dass Sie alle zuvor skalierten Werte in der Datenbank konvertieren.
Dieser Ansatz speichert für Menschen lesbare Werte (vorausgesetzt, Sie kennen den Skalierungsfaktor). Sie können mit SQL-Abfragen auch Berechnungen von direkt in der Datenbank gespeicherten Werten durchführen, sofern das Ergebnis korrekt skaliert wird und nicht überläuft.
Unskalierten ganzzahligen Wert und die Skalierung in separaten Spalten speichern
Sie können auch Zahlen mit beliebiger Genauigkeit in Spanner speichern, indem Sie zwei Elemente:
- Der nicht skalierte, ganzzahlige Wert, der in einem Byte-Array gespeichert ist
- Eine Ganzzahl, die den Skalierungsfaktor angibt
Zuerst konvertiert die Anwendung die beliebige Dezimalzahl in einen nicht skalierten, ganzzahligen Wert. Beispielsweise konvertiert die Anwendung 12.54321
in 1254321
.
Die Skala für dieses Beispiel ist 5
.
Dann konvertiert die Anwendung den nicht skalierten ganzzahligen Wert in ein Byte-Array unter Verwendung einer tragbaren Standard-Binärdarstellung (beispielsweise das Big-Endian-Zweierkomplement).
Die Datenbank speichert dann das Byte-Array (BYTES
/ BYTEA
) und die Ganzzahlskala (INT64
/ INT8
) in zwei separaten Spalten und konvertiert sie beim Lesen zurück.
In Java können Sie BigDecimal
verwenden.
und BigInteger
um folgende Berechnungen durchzuführen:
byte[] storedUnscaledBytes = bigDecimal.unscaledValue().toByteArray();
int storedScale = bigDecimal.scale();
Sie können mit folgendem Code in ein Java-BigDecimal
zurücklesen:
BigDecimal bigDecimal = new BigDecimal(
new BigInteger(storedUnscaledBytes),
storedScale);
Bei diesem Ansatz werden Werte mit beliebiger Genauigkeit und einer portablen Darstellung gespeichert. Die Werte sind jedoch in der Datenbank nicht lesbar und es ist erforderlich, alle Berechnungen von der Anwendung ausführen zu lassen.
Interne Darstellung der Anwendung als Byte speichern
Eine andere Option ist die Serialisierung der Dezimalwerte mit beliebiger Genauigkeit mit der internen Darstellung der Anwendung in Byte-Arrays und dann die Speicherung direkt in der Datenbank.
Die gespeicherten Datenbankwerte sind nicht lesbar und es ist erforderlich, dass die Anwendung alle Berechnungen durchführt.
Bei diesem Ansatz gibt es Probleme mit der Portabilität. Wenn Sie versuchen, die Werte mit einer anderen Programmiersprache oder Bibliothek zu lesen, als sie ursprünglich geschrieben wurden, funktioniert das möglicherweise nicht. Das Zurücklesen der Werte funktioniert möglicherweise deshalb nicht, weil verschiedene Bibliotheken mit beliebiger Genauigkeit unterschiedliche serialisierte Repräsentationen für Byte-Arrays haben können.
Nächste Schritte
- Weitere Informationen zu anderen Datentypen, die für Spanner verfügbar sind.
- Schemadesign und Datenmodell für Spanner richtig einrichten
- Schemadesign für Spanner optimieren