Spanner fornisce il tipo NUMERIC
che può memorizzare esattamente i numeri con precisione decimale.
La semantica del tipo NUMERIC
in Spanner varia in base ai due dialetti SQL (GoogleSQL e PostgreSQL), in particolare in relazione ai limiti di scala e precisione:
NUMERIC
nel dialetto PostgreSQL è un tipo numerico con precisione decimale arbitraria (la scala o la precisione può essere qualsiasi numero nell'intervallo supportato) e quindi è una scelta ideale per l'archiviazione di dati numerici con precisione arbitraria.NUMERIC
in GoogleSQL è un tipo numerico con precisione fissa (precisione=38 e scala=9) e non può essere utilizzato per memorizzare dati numerici di precisione arbitraria. Quando devi archiviare numeri di precisione arbitrari nei database di dialetti GoogleSQL, di memorizzarle 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 del separatore decimale in un numero. 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 con segno a 64 bit chiamato
INT64
nel dialetto GoogleSQL eINT8
nel dialetto PostgreSQL. - Tipo a virgola mobile con precisione binaria IEEE a 64 bit (doppio) denominato
FLOAT64
nel dialetto GoogleSQL eFLOAT8
nel dialetto PostgreSQL. - Tipo di precisione decimale
NUMERIC
.
Vediamoli in termini di precisione e scala.
INT64
/ INT8
rappresenta i valori numerici che non hanno un componente frazionario. Questo tipo di dati fornisce 18 cifre di
e la precisione, con una scala pari a zero.
FLOAT64
/FLOAT8
può rappresentare solo valori numerici decimali approssimativi con componenti frazionari e fornisce da 15 a 17 cifre significative (numero di cifre in un numero con tutti i zeri finali
rimossi) di precisione decimale. Diciamo che questo tipo rappresenta il numero decimale approssimato
perché la rappresentazione in virgola mobile IEEE a 64 bit
una rappresentazione binaria usata da Spanner non può rappresentare con precisione
frazioni decimali (in 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 la proprietà FLOAT64
/ FLOAT8
tipo di dati, la rappresentazione binaria viene riconvertita in un valore decimale
0,20000000000000001 (a 18 cifre di precisione). Analogamente (1,4 * 165) converte
torna a 230,999999999999971 e (0,1 + 0,2) torna a
0,30000000000000004. Questo è il motivo per cui i numeri in virgola mobile a 64 bit sono descritti come aventi solo 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 l'articolo Virgola in virgola mobile a precisione doppia
standard.
Né INT64
/INT8
né FLOAT64
/FLOAT8
hanno la precisione ideale per i calcoli finanziari, scientifici o ingegneristici, in cui in genere è richiesta una precisione di almeno 30 cifre.
Il tipo di dati NUMERIC
è adatto a queste applicazioni, in quanto è in grado di rappresentare valori numerici con precisione decimale esatta con una precisione di più di 30 cifre decimali.
I dati GoogleSQL di NUMERIC
può rappresentare numeri con precisione decimale fissa pari a 38 e scala fissa
di 9. L'intervallo di NUMERIC
di GoogleSQL è -9999999999999999999999999999.999999999
a 999999999999999999999999999.999999999.
Il tipo NUMERIC
del dialetto PostgreSQL può rappresentare numeri con una precisione decimale massima di 147.455 e una scala massima di 16.383.
Se devi archiviare numeri superiori ai valori di precisione e scala
offerte da NUMERIC
, le seguenti sezioni descrivono alcuni contenuti consigliati
soluzioni.
Consiglio: archivia i numeri di precisione arbitrari come stringhe
Quando devi memorizzare un numero di precisione arbitraria in un database Spanner e hai bisogno di una precisione maggiore di quella fornita da NUMERIC
, ti consigliamo di memorizzare 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 arbitraria dispone di metodi integrati per eseguire questa conversione senza perdita di dati. In Java, ad esempio, puoi utilizzare
BigDecimal.toPlainString()
e il BigDecimal(String)
come costruttore.
Memorizzare il numero come stringa ha il vantaggio che il valore venga memorizzato con
la precisione esatta (fino al limite di lunghezza delle colonne STRING
/ VARCHAR
) e il valore
rimangono leggibili da una persona.
Eseguire aggregazioni e calcoli esatti
Per eseguire aggregazioni e calcoli esatti sulle rappresentazioni di stringhe di numeri con precisione arbitraria, la tua applicazione deve eseguire questi calcoli. Non puoi utilizzare le funzioni di aggregazione SQL.
Ad esempio, per eseguire l'equivalente di un'istruzione SQL SUM(value)
su un intervallo
righe, l'applicazione deve eseguire una query sui valori stringa delle righe, quindi
convertirle e sommarle internamente nell'app.
Esegui aggregazioni, ordinamento e calcoli approssimativi
Puoi utilizzare le query SQL per eseguire calcoli aggregati approssimati per
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 memorizzare numeri con precisione arbitraria in Spanner. Se non memorizzare numeri di precisione arbitrari perché le stringhe non funzionano per di Google Cloud, prendi in considerazione le seguenti alternative:
Archivia valori interi per la scalabilità dell'applicazione
Per memorizzare numeri con precisione arbitraria, puoi eseguire la prescalatura dei valori prima della scrittura, in modo che i numeri vengano sempre memorizzati come numeri interi, e riscala i valori dopo la lettura. L'applicazione memorizza 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 memorizzato con una precisione di 5
posizioni decimali. L'applicazione converte il valore in un numero intero moltiplicandolo per 100.000 (spostando la virgola decimale di 5 posizioni a destra), pertanto il valore 12,54321 viene memorizzato come 1254321
.
In termini monetari, questo approccio è come memorizzare i valori in dollari come multipli di mille centesimi, in modo simile alla memorizzazione delle unità di tempo in millisecondi.
L'applicazione determina il fattore di scalabilità fisso. Se modifichi la scalabilità devi convertire tutti i valori scalati in precedenza nel database.
Questo approccio memorizza valori leggibili da una persona (supponendo che tu conosca i fattore di scala). Inoltre, puoi usare query SQL per eseguire direttamente i calcoli sui valori archiviati nel database, purché il risultato venga scalato correttamente senza overflow.
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 con precisione arbitraria in un valore intero non scalato. Ad esempio, l'applicazione converte 12.54321
in 1254321
.
La scala per questo esempio è 5
.
L'applicazione converte quindi il valore intero non scalato in un array di byte utilizzando una rappresentazione binaria portatile standard (ad esempio, complemento a due big endian).
Il database memorizza quindi l'array di byte (BYTES
/ BYTEA
) e la scala di interi (INT64
/ INT8
)
in due colonne separate e li riconverte in fase di 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 memorizza i valori con una precisione arbitraria e una rappresentazione portatile, ma i valori non sono leggibili dall'uomo nel database e tutti i calcoli devono essere eseguiti dall'applicazione.
Memorizza la rappresentazione interna dell'applicazione sotto forma di byte
Un'altra opzione è serializzare i valori decimali di precisione arbitraria in byte utilizzando la rappresentazione interna dell'applicazione, per poi archiviarli direttamente nel database.
I valori del database archiviati non sono leggibili e l'applicazione deve eseguire tutti i calcoli.
Questo approccio presenta problemi di portabilità. Se provi a leggere i valori con un parametro un linguaggio di programmazione o una libreria diversa da quella che l'ha scritta in origine, potrebbe non funzionare. La lettura dei valori potrebbe non funzionare perché librerie di precisione arbitraria diverse 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 un modello di dati e una progettazione dello schema di Spanner.
- Scopri come ottimizzare la progettazione dello schema per Spanner.