¿Por qué el entero sin signo no está disponible en PostgreSQL?

113

Me encontré con esta publicación ( ¿Cuál es la diferencia entre tinyint, smallint, mediumint, bigint e int en MySQL? ) Y me di cuenta de que PostgreSQL no admite enteros sin signo.

¿Alguien puede ayudar a explicar por qué es así?

La mayoría de las veces, utilizo un entero sin firmar como clave primaria autoincrementada en MySQL. En tal diseño, ¿cómo puedo superar esto cuando transfiero mi base de datos de MySQL a PostgreSQL?

Gracias.

Adrian Hoe
fuente
Todavía no, pero pronto y estamos considerando cambiarnos a PostgreSQL.
Adrian Hoe
4
No creo que este sea el mejor lugar para preguntar por qué se tomaron ciertas decisiones, una de las listas de correo de PostgreSQL podría ser más adecuada. Si desea valores de incremento automático, utilice serial(1 a 2147483647) o bigserial(1 a 9223372036854775807). Un entero de 64 bits con signo probablemente ofrece espacio más que suficiente.
mu es demasiado corto
4
Gracias @muistooshort. Eso respondió al problema de la clave principal. Pero, ¿qué tal un tipo entero sin signo que no se incrementa automáticamente ni es clave principal? Tengo columnas que almacenan números enteros sin firmar que tienen un rango de 0 a 2 ^ 32.
Adrian Hoe
4
Una ejecución rápida de los documentos de PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) puede ser útil para ayudarte a tener una mejor idea de lo que es capaz de hacer PostgreSQL. La única razón por la que usaría MySQL en estos días es si ya había invertido mucho en él: PostgreSQL es rápido, está cargado de funciones útiles y creado por personas que son bastante paranoicas con sus datos. En mi opinión, por supuesto :)
mu es demasiado corto
Gracias de nuevo @muistooshort por los consejos.
Adrian Hoe

Respuestas:

47

Ya se ha respondido por qué postgresql carece de tipos sin firmar. Sin embargo, sugeriría usar dominios para tipos sin firmar.

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

El dominio es como un tipo pero con una restricción adicional.

Para un ejemplo concreto, podría usar

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

Esto es lo que psql da cuando trato de abusar del tipo.

DS1 = # seleccionar (346346 :: uint2);

ERROR: el valor del dominio uint2 viola la restricción de verificación "uint2_check"

Karl Tarbe
fuente
Pero supongo que usar este dominio cada vez que queremos una columna sin firmar tendría una sobrecarga en INSERT / UPDATE. Es mejor usar esto donde sea realmente necesario (lo cual es raro) y simplemente acostumbrarse a la idea de que el tipo de datos no pone el límite inferior que deseamos. Después de todo, también establece un límite superior que generalmente no tiene sentido desde un punto de vista lógico. Los tipos numéricos no están diseñados para hacer cumplir las restricciones de nuestras aplicaciones.
Federico Razzoli
El único problema con este enfoque es que está "desperdiciando" 15 bits de almacenamiento de datos que no se utilizan. Sin mencionar que el cheque también cuesta una pequeña cantidad de eficiencia. La mejor solución sería que Postgres agregara unsigned como tipo de primera clase. En una tabla con 20 millones de registros, con un campo indexado como este, está desperdiciando 40 MB de espacio en bits no utilizados. Si está abusando de eso en otras 20 mesas, ahora está desperdiciando 800 MB de espacio.
tpartee
85

No está en el estándar SQL, por lo que la necesidad general de implementarlo es menor.

Tener demasiados tipos de enteros diferentes hace que el sistema de resolución de tipos sea más frágil, por lo que existe cierta resistencia a agregar más tipos a la mezcla.

Dicho esto, no hay ninguna razón por la que no se pueda hacer. Es mucho trabajo.

Peter Eisentraut
fuente
35
Esta pregunta es tan popular que me he propuesto solucionarla: github.com/petere/pguint
Peter Eisentraut
Sin embargo, tener conversiones de entrada / salida para literales enteros sin signo sería muy útil. O incluso solo un to_charpatrón.
Bergi
37

Puede utilizar una restricción CHECK, por ejemplo:

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

Además, PostgreSQL tiene smallserial, serialy bigserialtipos de incremento automático.

TriAnMan
fuente
2
Una cosa para mencionar, no puede tener NULL en columnas que usan CHECK.
Minutis
1
@Minutis ¿está seguro de que no puede tener x x es nulo o entre 4 y 40
jgmjgm
Y esto no le da la misma resolución que tendría si no estuviera firmado como int. Lo que significa que el int sin firmar podría subir 2^32-1, mientras que los ints firmados pueden subir 2^31-1.
JukesOnYou
2
NULLy CHECKson completamente ortogonales. Puede tener NULL/ NOT NULLcolumnas con o sin CHECK. Solo tenga en cuenta que, según la documentación en postgresql.org/docs/9.4/ddl-constraints.html , CHECKdevolver NULL se evalúa como TRUE, por lo que si realmente desea evitar NULL, utilice NOT NULLen su lugar (o además de CHECK).
flaviovs
el uso de un CHECK no me permite almacenar direcciones ipv4 en integer(no sin que sean aleatoriamente positivas o negativas, al menos ..)
hanshenrik
5

La charla sobre DOMINIOS es interesante pero no relevante para el único origen posible de esa pregunta. El deseo de ints sin firmar es duplicar el rango de ints con el mismo número de bits, es un argumento de eficiencia, no el deseo de excluir números negativos, todos saben cómo agregar una restricción de verificación.

Cuando alguien le preguntó al respecto , Tome Lane declaró:

Básicamente, hay cero posibilidades de que esto suceda a menos que pueda encontrar una manera de encajarlos en la jerarquía de promoción numérica que no rompa muchas aplicaciones existentes. Hemos examinado esto más de una vez, si la memoria no me falla, y no pudimos encontrar un diseño viable que no pareciera violar la POLA.

¿Qué es el "POLA"? Google me dio 10 resultados que no tienen sentido . No estoy seguro de si es un pensamiento políticamente incorrecto y, por lo tanto, está censurado. ¿Por qué este término de búsqueda no da ningún resultado? Lo que sea.

Puede implementar entradas sin firmar como tipos de extensión sin demasiados problemas. Si lo hace con funciones C, no habrá ninguna penalización de rendimiento. No necesitará extender el analizador para tratar con literales porque PgSQL tiene una manera tan fácil de interpretar cadenas como literales, simplemente escriba '4294966272' :: uint4 como sus literales. Los yesos tampoco deberían ser un gran problema. Ni siquiera necesita hacer excepciones de rango, simplemente puede tratar la semántica de '4294966273' :: uint4 :: int como -1024. O puede lanzar un error.

Si hubiera querido esto, lo habría hecho. Pero como estoy usando Java en el otro lado de SQL, para mí tiene poco valor ya que Java tampoco tiene esos enteros sin firmar. Entonces no gano nada. Ya estoy molesto si obtengo un BigInteger de una columna bigint, cuando debería caber en long.

Otra cosa, si tuviera la necesidad de almacenar tipos de 32 bits o 64 bits, puedo usar PostgreSQL int4 o int8 respectivamente, solo recordando que el orden natural o la aritmética no funcionarán de manera confiable. Pero el almacenamiento y la recuperación no se ven afectados por eso.


Así es como puedo implementar un int8 simple sin firmar:

Primero usaré

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
)

las 2 funciones mínimas uint8_iny uint8_outprimero debo definir.

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;

Necesito implementar esto en C uint8_funcs.c. Entonces uso el ejemplo complejo de aquí y lo hago 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, bueno, o puede encontrarlo ya hecho .

Gunther Schadow
fuente
1
Supongo que POLA es el "principio del menor asombro". Sugiere que el cambio tiene el potencial de cambiar el comportamiento existente de formas inesperadas.
Doctor Eval
1

De acuerdo con la documentación más reciente, se admite el entero sin signo, pero no el entero sin signo en la tabla. Sin embargo, el tipo de serie es similar a unsigned excepto que comienza desde 1 y no desde cero. Pero el límite superior es el mismo que chamuscado. Entonces, el sistema realmente no tiene soporte sin firmar. Como señaló Peter, la puerta está abierta para implementar la versión sin firmar. Es posible que el código tenga que actualizarse mucho, demasiado trabajo de mi experiencia trabajando con la programación 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
fuente
0

Postgres tiene un tipo entero sin signo que es desconocido para muchos: OID.

El oidtipo se implementa actualmente como un entero de cuatro bytes sin signo. […]

El oidtipo en sí tiene pocas operaciones más allá de la comparación. Sin embargo, se puede convertir a entero y luego manipular utilizando los operadores de enteros estándar. (Tenga cuidado con la posible confusión entre firmados y no firmados si hace esto).

Sin embargo, no es un tipo numérico , y tratar de hacer cualquier aritmética (o incluso operaciones bit a bit) con él va a fallar. Además, son solo 4 bytes ( INTEGER), no hay un BIGINTtipo sin firmar de 8 bytes ( ) correspondiente .

Por lo tanto, no es realmente una buena idea usar esto usted mismo, y estoy de acuerdo con todas las otras respuestas en que en un diseño de base de datos de Postgresql siempre debe usar una columna INTEGERo BIGINTpara su clave principal de serie , que comience en negativo ( MINVALUE) o lo permita to wrap around ( CYCLE) si desea agotar el dominio completo.

Sin embargo, es bastante útil para la conversión de entrada / salida, como su migración desde otro DBMS. Insertar el valor 2147483648en una columna de números enteros dará lugar a un " ERROR: número entero fuera de rango ", mientras que el uso de la expresión 2147483648::OIDfunciona bien.
Del mismo modo, al seleccionar una columna entera como texto con mycolumn::TEXT, obtendrá valores negativos en algún momento, pero con mycolumn::OID::TEXTsiempre obtendrá un número natural.

Vea un ejemplo en dbfiddle.uk .

Bergi
fuente
Si no necesita operaciones, entonces el único valor de usar OID es que su orden de clasificación funciona. Si eso es lo que necesitas, está bien. Pero pronto alguien querrá un uint8 y luego también se perderá. La conclusión es que para almacenar valores de 32 bits o 64 bits, puede usar int4 e int8 respectivamente, solo debe tener cuidado con las operaciones. Pero es fácil escribir una extensión.
Gunther Schadow