Spanner fornisce il tipo NUMERIC
in grado di archiviare esattamente i numeri di precisione decimali.
La semantica del tipo NUMERIC
in Spanner varia tra i due dialetti SQL (GoogleSQL e PostgreSQL), in particolare in base ai limiti di scalabilità e precisione:
NUMERIC
nel dialetto PostgreSQL è un tipo numerico di precisione decimale arbitraria (la scala o la precisione può essere qualsiasi numero compreso nell'intervallo supportato) e, di conseguenza, è la scelta ideale per archiviare dati numerici di precisione arbitraria.NUMERIC
in GoogleSQL è un tipo numerico a precisione fissa (precision=38 and scale=9) e non può essere utilizzato per archiviare dati numerici di precisione arbitraria. Quando devi archiviare numeri di precisione arbitrari nei database di dialetti GoogleSQL, ti consigliamo di archiviarli come stringhe.
Precisione dei tipi numerici di Spanner
La precisione è il numero di cifre di un numero. La scala è il numero di cifre a destra della virgola decimale Ad esempio, il numero 123,456 ha una precisione di 6 e una scala di 3. Spanner ha tre tipi numerici:
- Tipo di numero intero a 64 bit con segno denominato
INT64
nel dialetto GoogleSQL eINT8
nel dialetto PostgreSQL. - Tipo in virgola mobile con precisione binaria IEEE a 64 bit (doppio) denominato
FLOAT64
nel dialetto GoogleSQL eFLOAT8
nel dialetto PostgreSQL. - Tipo di precisione decimale
NUMERIC
.
Vediamole in termini di precisione e scalabilità.
INT64
/ INT8
rappresenta valori numerici che non hanno un componente frazionario. Questo tipo di dati fornisce 18 cifre di
precisione, con una scala da zero.
FLOAT64
/ FLOAT8
può rappresentare solo valori numerici decimali approssimati con componenti frazionari e fornisce
da 15 a 17 cifre significative (conteggio di cifre in un numero senza tutti gli zeri finali
rimossi) di precisione decimale. Diciamo che questo tipo rappresenta valori numerici decimali approssimati perché la rappresentazione binaria a virgola mobile IEEE a 64 bit che Spanner utilizza non può rappresentare con precisione le frazioni decimali (base 10) (può rappresentare esattamente solo frazioni in base 2).
Questa perdita di precisione introduce errori di arrotondamento per alcune frazioni decimali.
Ad esempio, quando memorizzi il valore decimale 0,2 utilizzando il tipo di dati FLOAT64
/ FLOAT8
,
la rappresentazione binaria viene convertita di nuovo in un valore decimale di
0,20000000000000001 (a 18 cifre di precisione). Analogamente (1,4 * 165) viene riconvertito
a 230,999999999999971 e (0,1 + 0,2) viene riconvertito
in 0,30000000000000004. Questo è il motivo per cui i numeri in virgola mobile a 64 bit sono definiti come solo con 15-17 cifre significative di precisione (solo alcuni numeri con più di 15 cifre decimali possono essere rappresentati come numeri in virgola mobile a 64 bit senza arrotondamento). Per ulteriori dettagli su come viene calcolata la precisione con rappresentazione in virgola mobile, consulta Formato con rappresentazione in virgola mobile a precisione doppia.
Né INT64
/ INT8
né FLOAT64
/ FLOAT8
presentano la precisione ideale per
i calcoli finanziari, scientifici o tecnici, per i quali è richiesta una precisione di almeno 30
cifre.
Il tipo di dati NUMERIC
è adatto per queste applicazioni, in quanto è in grado di rappresentare valori numerici con precisione decimale esatta con una precisione di più di 30 cifre decimali.
Il tipo di dati GoogleSQL NUMERIC
può rappresentare numeri con una precisione decimale fissa pari a 38 e una scala fissa pari a 9. L'intervallo di NUMERIC
di GoogleSQL è compreso tra -9999999999999999999999999999.999999999 e 9999999999
a 9999999999.
Il tipo di dialetto PostgreSQL NUMERIC
può rappresentare numeri con una precisione decimale massima di 147.455 e una scala massima di 16.383.
Se devi archiviare numeri superiori alla precisione e alla scalabilità offerte da NUMERIC
, le sezioni seguenti descrivono alcune soluzioni consigliate.
Consiglio: archivia i numeri di precisione arbitrari come stringhe
Quando devi archiviare un numero di precisione arbitrario in un database Spanner e hai bisogno di una precisione maggiore di quella fornita da NUMERIC
, ti consigliamo di archiviare il valore come rappresentazione decimale in una colonna STRING
/ VARCHAR
. Ad esempio, il numero 123.4
viene memorizzato come stringa "123.4"
.
Con questo approccio, l'applicazione deve eseguire una conversione senza perdita di dati tra la rappresentazione interna dell'applicazione del numero e il valore della colonna STRING
/ VARCHAR
per le letture e le scritture del database.
La maggior parte delle librerie di precisione arbitrarie dispone di metodi integrati per eseguire questa conversione senza perdita di dati. In Java, ad esempio, puoi utilizzare il metodo BigDecimal.toPlainString()
e il costruttore BigDecimal(String)
.
L'archiviazione del numero come stringa offre il vantaggio che il valore venga memorizzato con la precisione esatta (fino al limite di lunghezza delle colonne STRING
/ VARCHAR
) e che il valore rimanga leggibile.
Eseguire aggregazioni e calcoli esatti
Per eseguire aggregazioni e calcoli esatti sulle rappresentazioni stringa di numeri di precisione arbitrarie, l'applicazione deve eseguire questi calcoli. Non puoi utilizzare le funzioni di aggregazione SQL.
Ad esempio, per eseguire l'equivalente di un SUM(value)
di SQL su un intervallo di
righe, l'applicazione deve eseguire una query sui valori stringa per le righe, quindi
convertirli e sommarli internamente nell'app.
Esegui aggregazioni, ordinamenti e calcoli approssimati
Puoi utilizzare le query SQL per eseguire calcoli aggregati approssimati trasmettendo i valori a FLOAT64
/ FLOAT8
.
GoogleSQL
SELECT SUM(CAST(value AS FLOAT64)) FROM my_table
PostgreSQL
SELECT SUM(value::FLOAT8) FROM my_table
Allo stesso modo, puoi ordinare in base al valore numerico o ai valori limite per intervallo con la trasmissione di:
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;
Questi calcoli sono approssimativi rispetto ai limiti del tipo di dati FLOAT64
/ FLOAT8
.
Alternative
Esistono altri modi per archiviare numeri di precisione arbitrari in Spanner. Se l'archiviazione di numeri di precisione arbitrari come stringhe non funziona per la tua applicazione, prendi in considerazione le seguenti alternative:
Archivia valori interi per la scalabilità dell'applicazione
Per memorizzare numeri di precisione arbitrari, puoi prescalare i valori prima di
scriverli, in modo che i numeri vengano sempre memorizzati come numeri interi e riscalare i valori
dopo la lettura. L'applicazione archivia un fattore di scala fisso e la precisione è limitata alle 18 cifre fornite dal tipo di dati INT64
/ INT8
.
Prendiamo ad esempio un numero che deve essere archiviato con una precisione di 5 cifre decimali. L'applicazione converte il valore in un numero intero moltiplicandolo
per 100.000 (spostando il punto decimale di 5 posizioni a destra), quindi il valore
12,54321 viene memorizzato come 1254321
.
In termini monetari, questo approccio è come memorizzare i valori in dollari come multipli di milli centesimi, in modo simile all'archiviazione delle unità di tempo in millisecondi.
L'applicazione determina il fattore di scalabilità fisso. Se modifichi il fattore di scalabilità, devi convertire tutti i valori scalati in precedenza nel database.
Questo approccio archivia valori leggibili da una persona (supponendo che tu conosca il fattore di scalabilità). Inoltre, puoi utilizzare le query SQL per eseguire i calcoli direttamente sui valori archiviati nel database, purché il risultato sia scalato correttamente e non si sovrapponga.
Archivia il valore intero non scalato e la fare lo scale in colonne separate
Puoi anche archiviare numeri di precisione arbitrari in Spanner utilizzando due elementi:
- Il valore intero non scalato archiviato in un array di byte.
- Un numero intero che specifica il fattore di scala.
Innanzitutto, l'applicazione converte il numero decimale di precisione arbitrario in un valore intero non scalato. Ad esempio, l'applicazione converte 12.54321
in 1254321
.
La scala per questo esempio è 5
.
Quindi l'applicazione converte il valore intero non scalato in un array di byte utilizzando una rappresentazione binaria portatile standard (ad esempio, il complemento a due big-endian).
Il database archivia quindi l'array di byte (BYTES
/ BYTEA
) e la scala di numeri interi (INT64
/ INT8
) in due colonne separate e li converte di nuovo alla lettura.
In Java, puoi utilizzare BigDecimal
e BigInteger
per eseguire questi calcoli:
byte[] storedUnscaledBytes = bigDecimal.unscaledValue().toByteArray();
int storedScale = bigDecimal.scale();
Puoi rileggere un BigDecimal
Java utilizzando il seguente codice:
BigDecimal bigDecimal = new BigDecimal(
new BigInteger(storedUnscaledBytes),
storedScale);
Questo approccio archivia i valori con una precisione arbitraria e una rappresentazione portabile, ma i valori non sono leggibili da una persona nel database e tutti i calcoli devono essere eseguiti dall'applicazione.
Archivia la rappresentazione interna dell'applicazione come byte
Un'altra opzione è serializzare i valori decimali di precisione arbitrari in array di byte utilizzando la rappresentazione interna dell'applicazione, quindi archiviarli direttamente nel database.
I valori del database memorizzati non sono leggibili da una persona e l'applicazione deve eseguire tutti i calcoli.
Questo approccio presenta problemi di portabilità. Se provi a leggere i valori con un linguaggio di programmazione o una libreria diversa da quella che li ha creati, potrebbe non funzionare. La lettura dei valori potrebbe non funzionare perché diverse librerie di precisione arbitraria possono avere rappresentazioni serializzate diverse per gli array di byte.
Passaggi successivi
- Scopri di più sugli altri tipi di dati disponibili per Spanner.
- Scopri come configurare correttamente la progettazione dello schema e il modello dei dati di Spanner.
- Scopri come ottimizzare la progettazione dello schema per Spanner.