Dlaczego liczba całkowita bez znaku nie jest dostępna w PostgreSQL?

113

Natknąłem się na ten post ( Jaka jest różnica między tinyint, smallint, mediumint, bigint i int w MySQL? ) I zdałem sobie sprawę, że PostgreSQL nie obsługuje liczb całkowitych bez znaku.

Czy ktoś może pomóc wyjaśnić, dlaczego tak jest?

Przez większość czasu używam liczb całkowitych bez znaku jako automatycznie zwiększanego klucza podstawowego w MySQL. W takim projekcie, jak mogę temu zaradzić, przenosząc moją bazę danych z MySQL do PostgreSQL?

Dzięki.

Adrian Hoe
źródło
Jeszcze nie, ale wkrótce i rozważamy przejście na PostgreSQL.
Adrian Hoe
4
Nie sądzę, żeby to było najlepsze miejsce do zadawania pytań, dlaczego podjęto pewne decyzje, jedna z list dyskusyjnych PostgreSQL może być bardziej odpowiednia. Jeśli chcesz, aby wartości były automatycznie zwiększane, użyj serial(1 do 2147483647) lub bigserial(1 do 9223372036854775807). 64-bitowa liczba całkowita ze znakiem prawdopodobnie oferuje więcej niż wystarczającą ilość miejsca.
mu jest za krótkie
4
Dzięki @muistooshort. To odpowiadało na kwestię klucza podstawowego. Ale co z typem liczby całkowitej bez znaku, który nie jest automatycznie zwiększany ani kluczem podstawowym? Mam kolumny, które przechowują liczbę całkowitą bez znaku, która ma zakres od 0 do 2 ^ 32.
Adrian Hoe
4
Szybkie przejrzenie dokumentacji PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) może być przydatne, aby pomóc Ci lepiej zrozumieć, do czego jest zdolny PostgreSQL. Jedynym powodem, dla którego używałbym MySQL w dzisiejszych czasach, jest to, że już dużo w niego zainwestowałem: PostgreSQL jest szybki, wyposażony w przydatne funkcje i zbudowany przez ludzi, którzy mają dość paranoję na temat swoich danych. IMO oczywiście :)
mu jest za krótkie
Jeszcze raz dziękuję @muistooshort za wskazówki.
Adrian Hoe,

Odpowiedzi:

47

Jest już odpowiedź, dlaczego w postgresql brakuje typów bez znaku. Jednak sugerowałbym użycie domen dla typów niepodpisanych.

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

Domena jest podobna do typu, ale z dodatkowym ograniczeniem.

Na konkretny przykład możesz użyć

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

Oto co daje psql, kiedy próbuję nadużywać tego typu.

DS1 = # select (346346 :: uint2);

BŁĄD: wartość dla domeny uint2 narusza ograniczenie kontroli „uint2_check”

Karl Tarbe
źródło
Ale wydaje mi się, że używanie tej domeny za każdym razem, gdy potrzebujemy kolumny bez znaku, wiązałoby się z narzutem na INSERT / UPDATE. Lepiej użyć tego, gdy jest to naprawdę konieczne (co jest rzadkie) i po prostu przyzwyczaić się do idei, że typ danych nie nakłada dolnej granicy, której pragniemy. W końcu wyznacza również górną granicę, która zwykle jest bez znaczenia z logicznego punktu widzenia. Typy liczbowe nie są przeznaczone do wymuszania ograniczeń naszych aplikacji.
Federico Razzoli
Jedynym problemem związanym z tym podejściem jest to, że „marnujesz” 15 nieużywanych bitów pamięci. Nie wspominając o tym, że kontrola kosztuje również niewielką ilość wydajności. Lepszym rozwiązaniem byłby Postgres dodający unsigned jako typ pierwszej klasy. W tabeli z 20 milionami rekordów, z polami indeksowanymi i takimi jak to, marnujesz 40 MB miejsca na nieużywane bity. Jeśli nadużywasz tego na kolejnych 20 stołach, marnujesz teraz 800 MB miejsca.
uczestnik
85

Nie ma go w standardzie SQL, więc ogólna potrzeba jego implementacji jest mniejsza.

Zbyt wiele różnych typów liczb całkowitych sprawia, że ​​system rozwiązywania typów jest bardziej kruchy, więc istnieje pewien opór przed dodawaniem kolejnych typów do miksu.

To powiedziawszy, nie ma powodu, dla którego nie można tego zrobić. To po prostu dużo pracy.

Peter Eisentraut
źródło
35
To pytanie jest na tyle popularne, że postanowiłem je naprawić: github.com/petere/pguint
Peter Eisentraut,
Konwersje wejścia / wyjścia dla literałów całkowitych bez znaku byłoby jednak bardzo przydatne. Albo po prostu to_charwzór.
Bergi
37

Możesz użyć ograniczenia CHECK, np .:

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

Ponadto, PostgreSQL ma smallserial, seriali bigserialtypy dla automatycznego.

TriAnMan
źródło
2
Warto wspomnieć, że nie możesz mieć żadnych wartości NULL w kolumnach korzystających z opcji CHECK.
Minutis
1
@Minutis czy na pewno nie możesz mieć x IS NULL OR x BETWEEN 4 AND 40
jgmjgm
I to nie daje takiej samej rozdzielczości, jak w przypadku braku znaku int. Oznacza to, że liczba int bez znaku może wzrosnąć do 2^32-1, tymczasem liczba int ze znakiem może wzrosnąć do 2^31-1.
JukesOnYou
2
NULLi CHECKsą całkowicie ortogonalne. Możesz mieć NULL/ NOT NULLkolumny z lub bez CHECK. Zwróć uwagę, że zgodnie z dokumentacją na postgresql.org/docs/9.4/ddl-constraints.html , CHECKzwracanie wartości NULL do TRUE, więc jeśli naprawdę chcesz zapobiec NULL, użyj NOT NULLzamiast tego (lub dodatkowo CHECK).
flaviovs
użycie CHECK nie pozwala mi na przechowywanie adresów IPv4 integer(nie bez losowego, przynajmniej dodatniego lub ujemnego wyniku)
hanshenrik
5

Rozmowa o DOMENACH jest interesująca, ale nie dotyczy jedynego możliwego źródła tego pytania. Dążenie do liczby int bez znaku polega na podwojeniu zakresu liczb całkowitych przy tej samej liczbie bitów, jest to argument wydajności, a nie chęć wykluczenia liczb ujemnych, każdy wie, jak dodać ograniczenie sprawdzające.

Kiedy zapytany przez kogoś o tym , Tome Lane stwierdził:

Zasadniczo istnieje zerowa szansa, że ​​tak się stanie, jeśli nie znajdziesz sposobu na dopasowanie ich do numerycznej hierarchii promocji, który nie zepsuje wielu istniejących aplikacji. Patrzyliśmy na to więcej niż raz, jeśli pamięć służy, i nie udało nam się stworzyć działającego projektu, który nie wydawałby się naruszać POLA.

Co to jest „POLA”? Google dał mi 10 wyników, które są bez znaczenia . Nie jestem pewien, czy jest to myśl politycznie niepoprawna, a zatem ocenzurowana. Dlaczego to wyszukiwane hasło nie daje żadnych wyników? Cokolwiek.

Możesz implementować niepodpisane liczby int jako typy rozszerzeń bez większych problemów. Jeśli zrobisz to za pomocą funkcji C, nie będzie żadnych kar za wydajność. Nie musisz rozszerzać parsera, aby zajmował się literałami, ponieważ PgSQL ma tak łatwy sposób interpretowania łańcuchów jako literałów, po prostu wpisz '4294966272' :: uint4 jako swoje literały. Rzuty też nie powinny być wielkim problemem. Nie musisz nawet robić wyjątków zakresów, możesz po prostu traktować semantykę '4294966273' :: uint4 :: int jako -1024. Lub możesz zgłosić błąd.

Gdybym tego chciał, zrobiłbym to. Ale ponieważ używam Javy po drugiej stronie SQL, ma to dla mnie niewielką wartość, ponieważ Java również nie ma tych liczb całkowitych bez znaku. Więc nic nie zyskuję. Jestem już zirytowany, gdy dostaję BigInteger z kolumny biginta, kiedy powinien pasować do long.

Inna sprawa, gdybym miał potrzebę przechowywania typów 32-bitowych lub 64-bitowych, mogę użyć odpowiednio PostgreSQL int4 lub int8, pamiętając tylko, że naturalna kolejność lub arytmetyka nie będą działać niezawodnie. Nie ma to jednak wpływu na przechowywanie i odzyskiwanie.


Oto jak mogę zaimplementować proste bez znaku int8:

Najpierw użyję

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
)

minimum 2 funkcje uint8_ini uint8_outmuszę najpierw zdefiniować.

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;

trzeba to zaimplementować w C uint8_funcs.c. Więc skorzystam ze złożonego przykładu z tego miejsca i uproszczę to:

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

no cóż, albo możesz po prostu znaleźć to już zrobione .

Gunther Schadow
źródło
1
Domyślam się, że POLA to „zasada najmniejszego zdziwienia”. Sugeruje, że zmiana może zmienić istniejące zachowanie w nieoczekiwany sposób.
Doctor Eval
1

Zgodnie z najnowszą dokumentacją, pojedyncza liczba całkowita jest obsługiwana, ale w tabeli nie ma liczby całkowitej bez znaku. Jednak typ seryjny jest podobny do bez znaku, z wyjątkiem tego, że zaczyna się od 1, a nie od zera. Ale górna granica jest taka sama jak przypalona. Tak więc system naprawdę nie ma niepodpisanego wsparcia. Jak zauważył Piotr, drzwi są otwarte do realizacji wersji bez znaku. Kod może wymagać częstej aktualizacji, po prostu zbyt dużo pracy z mojego doświadczenia w programowaniu w 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
źródło
0

Postgres ma niepodpisane typ całkowitą, która jest bez wiedzy wiele: OID.

oidTyp jest obecnie realizowane bez znaku czterech bajtów całkowitej. […]

Sam oidtyp ma kilka operacji poza porównaniem. Można go jednak rzutować na liczbę całkowitą, a następnie manipulować przy użyciu standardowych operatorów całkowitych. (Uważaj na możliwe pomyłki ze znakiem i bez znaku, jeśli to zrobisz).

Nie jest to jednak typ numeryczny i próba wykonania na nim jakichkolwiek działań arytmetycznych (a nawet operacji bitowych) zakończy się niepowodzeniem. Poza tym to tylko 4 bajty ( INTEGER), nie ma odpowiadającego mu typu 8 bajtów ( BIGINT) bez znaku.

Więc nie jest dobrym pomysłem używanie tego samemu i zgadzam się ze wszystkimi innymi odpowiedziami, że w projekcie bazy danych Postgresql powinieneś zawsze używać kolumny INTEGERlub BIGINTdla swojego seryjnego klucza podstawowego - zaczynając od negacji ( MINVALUE) lub pozwalając na to wrap around ( CYCLE), jeśli chcesz wyczerpać całą domenę.

Jest to jednak przydatne do konwersji wejścia / wyjścia, jak migracja z innego DBMS. Wstawienie wartości 2147483648do kolumny z liczbami całkowitymi spowoduje wyświetlenie komunikatu „ BŁĄD: liczba całkowita poza zakresem ”, podczas gdy użycie wyrażenia 2147483648::OIDdziała dobrze.
Podobnie, wybierając kolumnę z liczbą całkowitą jako tekst z mycolumn::TEXT, w pewnym momencie otrzymasz wartości ujemne, ale mycolumn::OID::TEXTzawsze otrzymasz liczbę naturalną.

Zobacz przykład na dbfiddle.uk .

Bergi
źródło
Jeśli nie potrzebujesz operacji, jedyną wartością wynikającą z używania OID jest to, że działa porządek sortowania. Jeśli tego potrzebujesz, w porządku. Ale wkrótce ktoś będzie chciał uint8 i wtedy też się zgubią. Najważniejsze jest to, że aby przechowywać wartości 32-bitowe lub 64-bitowe, możesz po prostu użyć odpowiednio int4 i int8, po prostu musisz zachować ostrożność podczas wykonywania operacji. Ale łatwo jest napisać rozszerzenie.
Gunther Schadow