Pourquoi un entier non signé n'est pas disponible dans PostgreSQL?

113

Je suis tombé sur ce post ( Quelle est la différence entre tinyint, smallint, mediumint, bigint et int dans MySQL? ) Et réalisé que PostgreSQL ne prend pas en charge les entiers non signés.

Quelqu'un peut-il aider à expliquer pourquoi il en est ainsi?

La plupart du temps, j'utilise un entier non signé comme clé primaire auto-incrémentée dans MySQL. Dans une telle conception, comment puis-je surmonter cela lorsque je porte ma base de données de MySQL vers PostgreSQL?

Merci.

Adrian Hoe
la source
Pas encore mais bientôt et nous envisageons de passer à PostgreSQL.
Adrian Hoe
4
Je ne pense pas que ce soit le meilleur endroit pour se demander pourquoi certaines décisions ont été prises, l'une des listes de diffusion PostgreSQL pourrait être plus appropriée. Si vous voulez des valeurs à incrémentation automatique, utilisez serial(1 à 2147483647) ou bigserial(1 à 9223372036854775807). Un entier 64 bits signé offre probablement plus de place qu'assez.
mu est trop court
4
Merci @muistooshort. Cela a répondu au problème de la clé primaire. Mais qu'en est-il d'un type entier non signé qui n'est pas auto-incrémenté ni clé primaire? J'ai des colonnes qui stockent des entiers non signés qui ont une plage de 0 à 2 ^ 32.
Adrian Hoe
4
Un rapide tour d'horizon de la documentation PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) pourrait être utile pour vous aider à avoir une meilleure idée de ce dont PostgreSQL est capable. La seule raison pour laquelle j'utiliserais MySQL ces jours-ci est si j'y avais déjà beaucoup investi: PostgreSQL est rapide, chargé de fonctionnalités utiles et construit par des personnes assez paranoïdes à propos de leurs données. OMI bien sûr :)
mu est trop court
Merci encore @muistooshort pour les pointeurs.
Adrian Hoe

Réponses:

47

On a déjà répondu pourquoi postgresql manque de types non signés. Cependant, je suggérerais d'utiliser des domaines pour les types non signés.

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

Le domaine est comme un type mais avec une contrainte supplémentaire.

Pour un exemple concret, vous pourriez utiliser

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

Voici ce que donne psql lorsque j'essaye d'abuser du type.

DS1 = # select (346346 :: uint2);

ERREUR: la valeur du domaine uint2 enfreint la contrainte de vérification "uint2_check"

Karl Tarbe
la source
Mais je suppose que l'utilisation de ce domaine chaque fois que nous voulons une colonne non signée entraînerait une surcharge sur INSERT / UPDATE. Mieux vaut l'utiliser là où c'est vraiment nécessaire (ce qui est rare) et s'habituer simplement à l'idée que le type de données ne met pas la limite inférieure souhaitée. Après tout, cela met également une limite supérieure qui n'a généralement pas de sens d'un point de vue logique. Les types numériques ne sont pas conçus pour appliquer les contraintes de nos applications.
Federico Razzoli
Le seul problème avec cette approche est que vous «gaspillez» 15 bits de stockage de données qui sont inutilisés. Sans oublier que le chèque coûte également une petite quantité d'efficacité. La meilleure solution serait que Postgres ajoute non signé comme premier type de classe. Dans une table avec 20 millions d'enregistrements, avec un champ indexé comme celui-ci, vous perdez 40 Mo d'espace sur les bits inutilisés. Si vous en abusez sur 20 autres tables, vous perdez maintenant 800 Mo d'espace.
tpartee
85

Ce n'est pas dans la norme SQL, donc l'envie générale de l'implémenter est plus faible.

Le fait d'avoir trop de types d'entiers différents rend le système de résolution de type plus fragile, il y a donc une certaine résistance à l'ajout de types supplémentaires dans le mélange.

Cela dit, il n'y a aucune raison pour que cela ne puisse pas être fait. C'est juste beaucoup de travail.

Peter Eisentraut
la source
35
Cette question est suffisamment populaire pour que je me propose de la résoudre
Peter Eisentraut
Avoir des conversions d'entrée / sortie pour les littéraux entiers non signés serait cependant très utile. Ou même juste un to_charmodèle.
Bergi
37

Vous pouvez utiliser une contrainte CHECK, par exemple:

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

De plus, PostgreSQL a smallserial, serialet des bigserialtypes pour l'incrémentation automatique.

TriAnMan
la source
2
Une chose à mentionner, vous ne pouvez pas avoir de NULL dans les colonnes qui utilisent CHECK.
Minutis le
1
@Minutis êtes-vous sûr que vous ne pouvez pas avoir x EST NULL OU x ENTRE 4 ET 40
jgmjgm
Et cela ne vous donne pas la même résolution que s'il s'agissait d'un int non signé. Cela signifie que les entiers non signés peuvent aller jusqu'à 2^32-1, tandis que les entiers signés peuvent aller jusqu'à 2^31-1.
JukesOnYou
2
NULLet CHECKsont complètement orthogonales. Vous pouvez avoir NULL/ NOT NULLcolonnes avec ou sans CHECK. Notez simplement que, selon la documentation sur postgresql.org/docs/9.4/ddl-constraints.html , CHECKrenvoyer NULL donne la valeur TRUE, donc si vous voulez vraiment empêcher les NULL, utilisez à la NOT NULLplace (ou en plus de CHECK).
flaviovs
l'utilisation d'un CHECK ne me permet pas de stocker des adresses ipv4 integer(non sans qu'elles soient aléatoirement positives ou négatives, au moins ..)
hanshenrik
5

Le discours sur DOMAINES est intéressant mais pas pertinent pour la seule origine possible de cette question. Le désir des entiers non signés est de doubler la plage des entiers avec le même nombre de bits, c'est un argument d'efficacité, pas le désir d'exclure les nombres négatifs, tout le monde sait comment ajouter une contrainte de vérification.

Lorsque demandé par quelqu'un à ce sujet , Tome Lane a déclaré:

Fondamentalement, il n'y a aucune chance que cela se produise à moins que vous ne trouviez un moyen de les intégrer dans la hiérarchie de promotion numérique qui ne casse pas beaucoup d'applications existantes. Nous avons examiné cela plus d'une fois, si ma mémoire est bonne, et n'avons pas réussi à proposer une conception réalisable qui ne semblait pas violer la POLA.

Qu'est-ce que le "POLA"? Google m'a donné 10 résultats qui n'ont aucun sens . Je ne sais pas si c'est une pensée politiquement incorrecte et donc censurée. Pourquoi ce terme de recherche ne donnerait-il aucun résultat? Peu importe.

Vous pouvez implémenter des entiers non signés en tant que types d'extension sans trop de problèmes. Si vous le faites avec des fonctions C, il n'y aura pratiquement aucune pénalité de performance. Vous n'aurez pas besoin d'étendre l'analyseur pour traiter les littéraux car PgSQL a un moyen si simple d'interpréter les chaînes comme des littéraux, écrivez simplement '4294966272' :: uint4 comme vos littéraux. Les moulages ne devraient pas non plus être un problème. Vous n'avez même pas besoin de faire des exceptions de plage, vous pouvez simplement traiter la sémantique de '4294966273' :: uint4 :: int comme -1024. Ou vous pouvez lancer une erreur.

Si je le voulais, je l'aurais fait. Mais comme j'utilise Java de l'autre côté de SQL, pour moi, cela n'a guère de valeur puisque Java n'a pas non plus ces entiers non signés. Alors je ne gagne rien. Je suis déjà ennuyé si j'obtiens un BigInteger d'une colonne bigint, alors qu'il devrait tenir dans long.

Une autre chose, si j'avais besoin de stocker des types 32 bits ou 64 bits, je peux utiliser PostgreSQL int4 ou int8 respectivement, en me souvenant simplement que l'ordre naturel ou l'arithmétique ne fonctionnera pas de manière fiable. Mais le stockage et la récupération ne sont pas affectés par cela.


Voici comment je peux implémenter un simple int8 non signé:

Je vais d'abord utiliser

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
)

les 2 fonctions minimales uint8_inet uint8_outje dois d'abord définir.

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;

besoin de l'implémenter dans C uint8_funcs.c. Je vais donc utiliser l'exemple complexe d'ici et le rendre simple:

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 bien, ou vous pouvez juste le trouver déjà fait .

Gunther Schadow
la source
1
J'imagine que POLA est le "principe du moindre étonnement". Cela suggère que le changement a le potentiel de changer le comportement existant de manière inattendue.
Doctor Eval le
1

Selon la dernière documentation, l'entier singé est pris en charge mais pas d'entier non signé dans la table. Cependant, le type de série est un peu similaire à unsigned sauf qu'il commence à 1 et non à zéro. Mais la limite supérieure est la même que celle chantée. Le système n'a donc vraiment pas de support non signé. Comme l'a souligné Peter, la porte est ouverte pour mettre en œuvre la version non signée. Le code devra peut-être être mis à jour beaucoup, trop de travail de mon expérience de travail avec la programmation 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
Kemin Zhou
la source
0

Postgres a un type entier non signé qui est à beaucoup unbeknownst: OID.

Le oidtype est actuellement implémenté sous la forme d'un entier non signé de quatre octets. […]

Le oidtype lui-même a peu d'opérations au-delà de la comparaison. Il peut cependant être converti en entier, puis manipulé à l'aide des opérateurs d'entiers standard. (Méfiez-vous de la confusion possible entre signés et non signés si vous faites cela.)

Ce n'est pas un type numérique cependant, et essayer de faire de l'arithmétique (ou même des opérations au niveau du bit) avec lui échouera. De plus, il ne s'agit que de 4 octets ( INTEGER), il n'y a pas de BIGINTtype non signé correspondant à 8 octets ( ).

Ce n'est donc pas vraiment une bonne idée de l'utiliser vous-même, et je suis d'accord avec toutes les autres réponses que dans une conception de base de données Postgresql, vous devez toujours utiliser une colonne INTEGERou BIGINTpour votre clé primaire série - en la faisant commencer par la négative ( MINVALUE) ou en l'autorisant pour boucler ( CYCLE) si vous souhaitez épuiser le domaine complet.

Cependant, il est assez utile pour la conversion d'entrée / sortie, comme votre migration depuis un autre SGBD. L'insertion de la valeur 2147483648dans une colonne entière conduira à une " ERREUR: entier hors plage ", tandis que l'utilisation de l'expression 2147483648::OIDfonctionne très bien.
De même, lorsque vous sélectionnez une colonne entière sous forme de texte avec mycolumn::TEXT, vous obtiendrez des valeurs négatives à un moment donné, mais avec mycolumn::OID::TEXTvous obtiendrez toujours un nombre naturel.

Voir un exemple sur dbfiddle.uk .

Bergi
la source
Si vous n'avez pas besoin d'opérations, la seule valeur de l'utilisation de l'OID est que votre ordre de tri fonctionne. Si c'est ce dont vous avez besoin, très bien. Mais bientôt quelqu'un voudra un uint8 et ensuite ils sont perdus aussi. L'essentiel est que pour stocker des valeurs 32 bits ou 64 bits, vous pouvez simplement utiliser respectivement int4 et int8, il suffit de faire attention aux opérations. Mais il est facile d'écrire une extension.
Gunther Schadow le