Por que o inteiro sem sinal não está disponível no PostgreSQL?

113

Me deparei com este post ( Qual é a diferença entre tinyint, smallint, mediumint, bigint e int no MySQL? ) E percebi que o PostgreSQL não oferece suporte a inteiros sem sinal.

Alguém pode ajudar a explicar porque é assim?

Na maioria das vezes, eu uso inteiros sem sinal como chave primária com incremento automático no MySQL. Nesse projeto, como posso superar isso quando transporto meu banco de dados do MySQL para o PostgreSQL?

Obrigado.

Adrian Hoe
fonte
Ainda não, mas em breve e estamos considerando mudar para o PostgreSQL.
Adrian Hoe
4
Não acho que este seja o melhor lugar para perguntar por que certas decisões foram tomadas, uma das listas de discussão do PostgreSQL pode ser mais adequada. Se você quiser valores de incremento automático, use serial(1 a 2147483647) ou bigserial(1 a 9223372036854775807). Um inteiro de 64 bits assinado provavelmente oferece espaço mais do que suficiente.
mu é muito curto
4
Obrigado @muistooshort. Isso respondeu à questão da chave primária. Mas e quanto a um tipo inteiro sem sinal que não é incrementado automaticamente nem é uma chave primária? Eu tenho colunas que armazenam inteiros sem sinal, que variam de 0 a 2 ^ 32.
Adrian Hoe
4
Uma rápida execução dos documentos do PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) pode ser útil para ajudá-lo a ter uma ideia melhor do que o PostgreSQL é capaz. A única razão pela qual eu usaria o MySQL atualmente é se eu já tivesse investido muito nele: PostgreSQL é rápido, carregado com recursos úteis e construído por pessoas que são muito paranóicas com seus dados. IMO, é claro :)
mu é muito curto
Obrigado novamente @muistooshort pelas dicas.
Adrian Hoe

Respostas:

47

Já foi respondido porque o postgresql carece de tipos não assinados. No entanto, gostaria de sugerir o uso de domínios para tipos não assinados.

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

O domínio é como um tipo, mas com uma restrição adicional.

Para um exemplo concreto, você pode usar

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

Aqui está o que o psql oferece quando tento abusar do tipo.

DS1 = # select (346346 :: uint2);

ERRO: o valor para o domínio uint2 viola a restrição de verificação "uint2_check"

Karl Tarbe
fonte
Mas eu acho que usar este domínio toda vez que quisermos uma coluna sem sinal teria uma sobrecarga em INSERT / UPDATE. Melhor usar isso onde realmente é necessário (o que é raro) e se acostumar com a ideia de que o tipo de dados não coloca o limite inferior que desejamos. Afinal, também impõe um limite superior que normalmente não tem sentido do ponto de vista lógico. Os tipos numéricos não são projetados para impor as restrições de nossos aplicativos.
Federico Razzoli
O único problema com essa abordagem é que você está "desperdiçando" 15 bits de armazenamento de dados não utilizados. Sem mencionar que a verificação também custa uma pequena quantidade de eficiência. A melhor solução seria Postgres adicionando unsigned como um tipo de primeira classe. Em uma tabela com 20 milhões de registros, com um campo indexado como este, você está desperdiçando 40 MB de espaço em bits não utilizados. Se você está abusando disso em outras 20 tabelas, agora está desperdiçando 800 MB de espaço.
tpartee
85

Não está no padrão SQL, então a necessidade geral de implementá-lo é menor.

Ter muitos tipos de inteiros diferentes torna o sistema de resolução de tipo mais frágil, então há alguma resistência em adicionar mais tipos à mistura.

Dito isso, não há razão para que isso não possa ser feito. É apenas muito trabalho.

Peter Eisentraut
fonte
35
Esta questão é popular o suficiente para que eu me propusesse consertá-
Peter Eisentraut
Ter conversões de entrada / saída para literais inteiros não assinados seria muito útil. Ou mesmo apenas um to_charpadrão.
Bergi,
37

Você pode usar uma restrição CHECK, por exemplo:

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

Além disso, o PostgreSQL tem smallserial, seriale bigserialtipos para incremento automático.

TriAnMan
fonte
2
Uma coisa a mencionar, você não pode ter NULLs nas colunas que usam CHECK.
Minutis de
1
@Minutis você tem certeza que não pode ter x É NULO OU x ENTRE 4 E 40
jgmjgm
E isso não dá a você a mesma resolução que daria se fosse int não assinado. Significa que os int não assinados podem subir para 2^32-1, enquanto os ints assinados podem subir para 2^31-1.
JukesOnYou
2
NULLe CHECKsão completamente ortogonais. Você pode ter NULL/ NOT NULLcolunas com ou sem CHECK. Apenas observe que, de acordo com a documentação em postgresql.org/docs/9.4/ddl-constraints.html , CHECKretornar NULL é avaliado como TRUE, portanto, se você realmente deseja evitar NULLs, use em seu NOT NULLlugar (ou além de CHECK).
flaviovs
usar um CHECK não me permite armazenar endereços ipv4 integer(não sem que eles sejam aleatoriamente positivos ou negativos, pelo menos ..)
hanshenrik
5

A conversa sobre DOMAINS é interessante, mas não relevante para a única origem possível dessa questão. O desejo de ints sem sinal é dobrar o intervalo de ints com o mesmo número de bits, é um argumento de eficiência, não o desejo de excluir números negativos, todo mundo sabe como adicionar uma restrição de verificação.

Quando questionado por alguém sobre isso , Tome Lane afirmou:

Basicamente, a chance de isso acontecer é zero, a menos que você encontre uma maneira de encaixá-los na hierarquia de promoção numérica que não interrompa muitos dos aplicativos existentes. Já vimos isso mais de uma vez, se não me falha a memória, e não conseguimos encontrar um design viável que não parecesse violar o POLA.

O que é "POLA"? O Google me deu 10 resultados sem sentido . Não tenho certeza se é pensamento politicamente incorreto e, portanto, censurado. Por que este termo de pesquisa não produziria nenhum resultado? Tanto faz.

Você pode implementar ints não assinados como tipos de extensão sem muitos problemas. Se você fizer isso com funções C, não haverá nenhuma penalidade de desempenho. Você não precisará estender o analisador para lidar com literais porque o PgSQL tem uma maneira fácil de interpretar strings como literais, basta escrever '4294966272' :: uint4 como seus literais. Casts também não deveriam ser um grande negócio. Você nem mesmo precisa fazer exceções de intervalo, você pode apenas tratar a semântica de '4294966273' :: uint4 :: int como -1024. Ou você pode lançar um erro.

Se eu quisesse, eu teria feito. Mas como estou usando Java no outro lado do SQL, para mim tem pouco valor, já que o Java também não tem esses inteiros sem sinal. Portanto, não ganho nada. Já fico chateado se obtiver um BigInteger de uma coluna bigint, quando deveria caber em longa.

Outra coisa, se eu tivesse a necessidade de armazenar tipos de 32 ou 64 bits, posso usar PostgreSQL int4 ou int8 respectivamente, apenas lembrando que a ordem natural ou aritmética não funcionará de forma confiável. Mas o armazenamento e a recuperação não são afetados por isso.


Aqui está como posso implementar um int8 simples sem sinal:

Primeiro vou 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
)

as 2 funções mínimas uint8_ine uint8_outdevo primeiro 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;

precisa implementar isso em C uint8_funcs.c. Então, vou usar o exemplo complexo a partir daqui e torná-lo simples:

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

bem, ou você pode simplesmente descobrir que está pronto .

Gunther Schadow
fonte
1
Acho que POLA é o "princípio do menor espanto". Isso sugere que a mudança tem o potencial de mudar o comportamento existente de maneiras inesperadas.
Doutor Eval
1

De acordo com a documentação mais recente, o inteiro marcado é suportado, mas nenhum inteiro sem sinal na tabela. No entanto, o tipo de série é semelhante ao sem sinal, exceto que começa de 1 e não de zero. Mas o limite superior é o mesmo que chamuscado. Portanto, o sistema realmente não tem suporte não assinado. Conforme apontado por Peter, a porta está aberta para implementar a versão não assinada. O código pode ter que ser muito atualizado, muito trabalho com a minha experiência de trabalho com programação 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
fonte
0

Postgres tem um tipo inteiro não assinado que é desconhecido para muitos: OID.

O oidtipo é atualmente implementado como um inteiro de quatro bytes sem sinal. […]

O oidpróprio tipo tem poucas operações além da comparação. Ele pode ser convertido em inteiro, no entanto, e então manipulado usando os operadores de inteiro padrão. (Tome cuidado com a possível confusão entre sinal e não sinal se você fizer isso.)

Não é um tipo numérico , porém, e tentar fazer qualquer aritmética (ou mesmo operações bit a bit) com ele vai falhar. Além disso, tem apenas 4 bytes ( INTEGER), não há nenhum BIGINTtipo sem sinal de 8 bytes ( ) correspondente .

Portanto, não é realmente uma boa ideia usar isso você mesmo, e eu concordo com todas as outras respostas que em um projeto de banco de dados Postgresql você deve sempre usar uma coluna INTEGERou BIGINTpara sua chave primária serial - começando no negativo ( MINVALUE) ou permitindo para wrap around ( CYCLE) se você deseja esgotar todo o domínio.

No entanto, é bastante útil para a conversão de entrada / saída, como a migração de outro DBMS. Inserir o valor 2147483648em uma coluna inteira levará a um " ERRO: inteiro fora do intervalo ", enquanto o uso da expressão 2147483648::OIDfunciona perfeitamente.
Da mesma forma, ao selecionar uma coluna inteira como texto com mycolumn::TEXT, você obterá valores negativos em algum ponto, mas com mycolumn::OID::TEXTvocê sempre obterá um número natural.

Veja um exemplo em dbfiddle.uk .

Bergi
fonte
Se você não precisa de operações, o único valor de usar OID é que sua ordem de classificação funciona. Se é isso que você precisa, ótimo. Mas logo alguém vai querer um uint8 e então também se perderá. O resultado final é que, para armazenar valores de 32 ou 64 bits, você pode apenas usar int4 e int8 respectivamente, só precisa ter cuidado com as operações. Mas é fácil escrever uma extensão.
Gunther Schadow