Perché il numero intero senza segno non è disponibile in PostgreSQL?


113

Mi sono imbattuto in questo post ( Qual è la differenza tra tinyint, smallint, mediumint, bigint e int in MySQL? ) E mi sono reso conto che PostgreSQL non supporta interi senza segno.

Qualcuno può aiutare a spiegare perché è così?

La maggior parte delle volte, utilizzo un numero intero senza segno come chiave primaria con incremento automatico in MySQL. In tale progettazione, come posso superare questo problema quando porto il mio database da MySQL a PostgreSQL?

Grazie.


Non ancora ma presto e stiamo valutando di passare a PostgreSQL.
Adrian Hoe

4
Non penso che questo sia il posto migliore per chiedere perché sono state prese certe decisioni, una delle mailing list di PostgreSQL potrebbe essere più adatta. Se si desidera aumentare automaticamente i valori, utilizzare serial(da 1 a 2147483647) o bigserial(da 1 a 9223372036854775807). Un numero intero a 64 bit con segno probabilmente offre spazio più che sufficiente.
mu è troppo breve

4
Grazie @muistooshort. Questo ha risposto alla questione chiave primaria. Ma che ne dici di un tipo intero senza segno che non è incrementato automaticamente né chiave primaria? Ho colonne che memorizzano un numero intero senza segno che ha un intervallo da 0 a 2 ^ 32.
Adrian Hoe

4
Una rapida lettura dei documenti di PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) potrebbe essere utile per aiutarti a farti un'idea migliore di ciò di cui è capace PostgreSQL. L'unico motivo per cui userei MySQL in questi giorni è se avessi già investito molto in esso: PostgreSQL è veloce, ricco di funzionalità utili e costruito da persone che sono piuttosto paranoiche sui loro dati. IMO ovviamente :)
mu è troppo breve

Grazie ancora @muistooshort per i suggerimenti.
Adrian Hoe

Risposte:


47

È già stato risposto perché a postgresql mancano i tipi non firmati. Tuttavia, suggerirei di utilizzare i domini per i tipi non firmati.

http://www.postgresql.org/docs/9.4/static/sql-createdomain.html

 CREATE DOMAIN name [ AS ] data_type
    [ COLLATE collation ]
    [ DEFAULT expression ]
    [ constraint [ ... ] ]
 where constraint is:
 [ CONSTRAINT constraint_name ]
 { NOT NULL | NULL | CHECK (expression) }

Il dominio è come un tipo ma con un vincolo aggiuntivo.

Per un esempio concreto potresti usare

CREATE DOMAIN uint2 AS int4
   CHECK(VALUE >= 0 AND VALUE < 65536);

Ecco cosa dà psql quando provo ad abusare del tipo.

DS1 = # select (346346 :: uint2);

ERRORE: il valore per il dominio uint2 viola il vincolo di controllo "uint2_check"


Ma immagino che l'utilizzo di questo dominio ogni volta che vogliamo una colonna non firmata avrebbe un overhead su INSERT / UPDATE. Meglio usarlo dove è veramente necessario (il che è raro) e abituarsi all'idea che il tipo di dati non pone il limite inferiore che desideriamo. Dopotutto, pone anche un limite superiore che di solito è privo di significato da un punto di vista logico. I tipi numerici non sono progettati per applicare i vincoli delle nostre applicazioni.
Federico Razzoli

L'unico problema con questo approccio è che stai "sprecando" 15 bit di memoria dati che sono inutilizzati. Per non parlare del controllo costa anche una piccola quantità di efficienza. La soluzione migliore sarebbe che Postgres aggiungesse unsigned come primo tipo di classe. In una tabella con 20 milioni di record, con un campo indicizzato come questo, stai sprecando 40 MB di spazio su bit inutilizzati. Se ne stai abusando su altri 20 tavoli, stai sprecando 800 MB di spazio.
partecipazione il

85

Non è nello standard SQL, quindi la necessità generale di implementarlo è inferiore.

Avere troppi tipi interi diversi rende il sistema di risoluzione dei tipi più fragile, quindi c'è una certa resistenza all'aggiunta di più tipi nel mix.

Detto questo, non c'è motivo per cui non possa essere fatto. È solo un sacco di lavoro.


35
Questa domanda è abbastanza popolare che ho deciso di risolverla: github.com/petere/pguint
Peter Eisentraut

Avere conversioni di input / output per letterali interi senza segno sarebbe tuttavia molto utile. O anche solo uno to_charschema.
Bergi

37

Puoi utilizzare un vincolo CHECK, ad esempio:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0)
);

Inoltre, PostgreSQL ha smallserial, seriale bigserialtipi per l'incremento automatico.


2
Una cosa da menzionare, non puoi avere NULL nelle colonne che usano CHECK.
Minutis

1
@Minutis sei sicuro di non poter avere x È NULLO O x TRA 4 E 40
jgmjgm

E questo non ti dà la stessa risoluzione che sarebbe se fosse int non firmato. Significa che gli int senza segno possono salire a 2^32-1, mentre gli int firmati possono salire a 2^31-1.
JukesOnYou

2
NULLe CHECKsono completamente ortogonali. Puoi avere NULL/ NOT NULLcolonne con o senza CHECK. Nota solo che, come da documentazione su postgresql.org/docs/9.4/ddl-constraints.html , CHECKrestituire NULL restituisce TRUE, quindi se vuoi davvero prevenire i NULL, usa NOT NULLinvece (o in aggiunta a CHECK).
flaviovs

l'utilizzo di un CONTROLLO non mi consente di memorizzare indirizzi ipv4 integer(non senza che siano casualmente positivi o negativi, almeno ..)
hanshenrik

5

Il discorso sui DOMINI è interessante ma non pertinente all'unica possibile origine di quella domanda. Il desiderio di int senza segno è di raddoppiare l'intervallo di int con lo stesso numero di bit, è un argomento di efficienza, non il desiderio di escludere numeri negativi, tutti sanno come aggiungere un vincolo di controllo.

Alla domanda di qualcuno al riguardo , Tome Lane ha dichiarato:

Fondamentalmente, non ci sono possibilità che ciò accada a meno che non si riesca a trovare un modo per inserirli nella gerarchia delle promozioni numeriche che non interrompa molte applicazioni esistenti. Abbiamo esaminato questo aspetto più di una volta, se la memoria serve, e non siamo riusciti a trovare un design funzionante che non sembrava violare il POLA.

Cos'è la "POLA"? Google mi ha dato 10 risultati privi di significato . Non sono sicuro che sia un pensiero politicamente scorretto e quindi censurato. Perché questo termine di ricerca non produce alcun risultato? Qualunque cosa.

Puoi implementare int senza segno come tipi di estensione senza troppi problemi. Se lo fai con le funzioni C, non ci saranno affatto penalità sulle prestazioni. Non sarà necessario estendere il parser per gestire i letterali perché PgSQL ha un modo così semplice per interpretare le stringhe come letterali, basta scrivere "4294966272" :: uint4 come letterali. Anche i cast non dovrebbero essere un grosso problema. Non è nemmeno necessario fare eccezioni di intervallo, puoi semplicemente trattare la semantica di "4294966273" :: uint4 :: int come -1024. Oppure puoi lanciare un errore.

Se avessi voluto questo, l'avrei fatto. Ma dal momento che sto usando Java sull'altro lato di SQL, per me è di scarso valore poiché Java non ha nemmeno quegli interi senza segno. Quindi non guadagno nulla. Sono già infastidito se ottengo un BigInteger da una colonna bigint, quando dovrebbe rientrare in long.

Un'altra cosa, se avessi la necessità di memorizzare i tipi a 32 o 64 bit, posso usare PostgreSQL int4 o int8 rispettivamente, ricordando solo che l'ordine naturale o l'aritmetica non funzioneranno in modo affidabile. Ma la memorizzazione e il recupero non ne sono influenzati.


Ecco come posso implementare un semplice int8 senza segno:

Per prima cosa userò

CREATE TYPE name (
    INPUT = uint8_in,
    OUTPUT = uint8_out
    [, RECEIVE = uint8_receive ]
    [, SEND = uint8_send ]
    [, ANALYZE = uint8_analyze ]
    , INTERNALLENGTH = 8
    , PASSEDBYVALUE ]
    , ALIGNMENT = 8
    , STORAGE = plain
    , CATEGORY = N
    , PREFERRED = false
    , DEFAULT = null
)

le 2 funzioni minime uint8_ine uint8_outdevo prima definire.

CREATE FUNCTION uint8_in(cstring)
    RETURNS uint8
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION uint64_out(complex)
    RETURNS cstring
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

è necessario implementarlo in C uint8_funcs.c. Quindi uso l'esempio complesso da qui e lo rendo semplice:

PG_FUNCTION_INFO_V1(complex_in);

Datum complex_in(PG_FUNCTION_ARGS) {
    char       *str = PG_GETARG_CSTRING(0);
    uint64_t   result;

    if(sscanf(str, "%llx" , &result) != 1)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for uint8: \"%s\"", str)));

    return (Datum)SET_8_BYTES(result);
}

ah beh, o puoi semplicemente trovarlo già fatto .


1
Immagino che POLA sia il "principio del minimo stupore". Suggerisce che il cambiamento ha il potenziale per modificare il comportamento esistente in modi inaspettati.
Doctor Eval

1

Secondo la documentazione più recente, il numero intero singed è supportato ma nessun numero intero senza segno nella tabella. Tuttavia, il tipo seriale è simile a unsigned tranne che inizia da 1 e non da zero. Ma il limite superiore è lo stesso di bruciato. Quindi il sistema non ha davvero un supporto non firmato. Come sottolineato da Peter, la porta è aperta per implementare la versione non firmata. Il codice potrebbe dover essere aggiornato molto, solo troppo lavoro dalla mia esperienza di lavoro con la programmazione C.

https://www.postgresql.org/docs/10/datatype-numeric.html

integer     4 bytes     typical choice for integer  -2147483648 to +2147483647
serial  4 bytes     autoincrementing integer    1 to 2147483647

0

Postgres ha un tipo intero senza segno che è all'insaputa di molti: OID.

Il oidtipo è attualmente implementato come intero a quattro byte senza segno. [...]

Il oidtipo stesso ha poche operazioni oltre il confronto. Tuttavia, può essere convertito in numero intero e quindi manipolato utilizzando gli operatori interi standard. (Fai attenzione alla possibile confusione tra segno e non segno se lo fai.)

Tuttavia, non è un tipo numerico e provare a eseguire operazioni aritmetiche (o anche operazioni bit per bit) con esso fallirà. Inoltre, sono solo 4 byte ( INTEGER), non esiste un BIGINTtipo senza segno corrispondente a 8 byte ( ).

Quindi non è proprio una buona idea usarlo da soli, e sono d'accordo con tutte le altre risposte che in un progetto di database Postgresql dovresti sempre usare una colonna INTEGERo BIGINTper la tua chiave primaria seriale - facendola iniziare in negativo ( MINVALUE) o consentendola per avvolgere ( CYCLE) se vuoi esaurire l'intero dominio.

Tuttavia, è abbastanza utile per la conversione di input / output, come la migrazione da un altro DBMS. L'inserimento del valore 2147483648in una colonna di numeri interi porterà a un " ERRORE: numero intero fuori intervallo ", mentre l'uso dell'espressione 2147483648::OIDfunziona perfettamente.
Allo stesso modo, quando selezioni una colonna di numeri interi come testo con mycolumn::TEXT, a un certo punto otterrai valori negativi, ma con mycolumn::OID::TEXTotterrai sempre un numero naturale.

Guarda un esempio su dbfiddle.uk .


Se non hai bisogno di operazioni, l'unico valore derivante dall'utilizzo dell'OID è che il tuo ordinamento funziona. Se è quello che ti serve, va bene. Ma presto qualcuno vorrà un uint8 e poi anche loro saranno persi. La linea di fondo è che per memorizzare valori a 32 o 64 bit puoi semplicemente usare int4 e int8 rispettivamente, devi solo fare attenzione con le operazioni. Ma è facile scrivere un'estensione.
Gunther Schadow il
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.