Почему целое число без знака недоступно в PostgreSQL?

115

Я наткнулся на этот пост (В чем разница между tinyint, smallint, mediumint, bigint и int в MySQL? ) И понял, что PostgreSQL не поддерживает целые числа без знака.

Может ли кто-нибудь помочь объяснить, почему это так?

В большинстве случаев я использую целое число без знака в качестве автоматически увеличивающегося первичного ключа в MySQL. Как я могу решить эту проблему при таком дизайне при переносе базы данных с MySQL на PostgreSQL?

Спасибо.

Адриан Хоу
источник
Еще нет, но скоро, и мы рассматриваем возможность перехода на PostgreSQL.
Адриан Хо
4
Я не думаю, что это лучшее место, чтобы спрашивать, почему были приняты определенные решения, один из списков рассылки PostgreSQL может быть более подходящим. Если вы хотите автоматически увеличивать значения, используйте serial(от 1 до 2147483647) или bigserial(от 1 до 9223372036854775807). 64-битное целое число со знаком, вероятно, предлагает более чем достаточно места.
mu слишком короткое
4
Спасибо @muistooshort. Это ответило на вопрос о первичном ключе. Но как насчет беззнакового целочисленного типа, который не увеличивается автоматически и не имеет первичного ключа? У меня есть столбцы, в которых хранится целое число без знака, которое имеет диапазон от 0 до 2 ^ 32.
Адриан Хоу
4
Быстрый просмотр документации PostgreSQL ( postgresql.org/docs/current/interactive/index.html ) может быть полезен, чтобы помочь вам лучше понять, на что способен PostgreSQL. Единственная причина, по которой я сейчас использую MySQL, - это то, что я уже много в нее вложил: PostgreSQL быстр, загружен полезными функциями и создан людьми, которые довольно параноидально относятся к своим данным. IMO конечно :)
mu слишком короткий
Еще раз спасибо @muistooshort за указатели.
Адриан Хоу

Ответы:

48

Уже есть ответ, почему postgresql не хватает беззнаковых типов. Однако я бы предложил использовать домены для беззнаковых типов.

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

Домен похож на тип, но с дополнительным ограничением.

Для конкретного примера вы можете использовать

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

Вот что дает psql, когда я пытаюсь злоупотребить типом.

DS1 = # выберите (346346 :: uint2);

ОШИБКА: значение для домена uint2 нарушает ограничение проверки "uint2_check"

Карл Тарбе
источник
Но я предполагаю, что использование этого домена каждый раз, когда нам нужен столбец без знака, будет иметь накладные расходы на INSERT / UPDATE. Лучше использовать это там, где это действительно необходимо (что бывает редко), и просто привыкнуть к мысли, что тип данных не устанавливает желаемый нижний предел. В конце концов, он также устанавливает верхний предел, который обычно не имеет смысла с логической точки зрения. Числовые типы не предназначены для принудительного применения ограничений наших приложений.
Федерико Разцоли
Единственная проблема с этим подходом состоит в том, что вы «тратите» 15 неиспользуемых битов хранилища данных. Не говоря уже о том, что проверка также стоит небольшого количества эффективности. Лучшим решением было бы Postgres добавить unsigned как тип первого класса. В таблице с 20 миллионами записей с подобным индексированным полем вы тратите 40 МБ пространства на неиспользуемые биты. Если вы злоупотребляете этим еще с 20 таблицами, теперь вы тратите 800 МБ пространства.
участник
85

Его нет в стандарте SQL, поэтому общая потребность в его реализации ниже.

Наличие слишком большого количества различных целочисленных типов делает систему разрешения типов более хрупкой, поэтому есть некоторое сопротивление добавлению большего количества типов в смесь.

Тем не менее, нет никаких причин, по которым этого нельзя было бы сделать. Это просто большая работа.

Питер Эйзентраут
источник
35
Этот вопрос достаточно популярен, и я решил его исправить: github.com/petere/pguint
Питер Эйзентраут,
Однако преобразование ввода / вывода для целочисленных литералов без знака было бы очень полезно. Или даже просто to_charвыкройка.
Берги
37

Вы можете использовать ограничение CHECK, например:

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

Кроме того , PostgreSQL имеет smallserial, serialи bigserialтипов для автоматического увеличения.

TriAnMan
источник
2
Следует упомянуть, что у вас не может быть никаких NULL в столбцах, использующих CHECK.
Minutis
1
@Minutis, вы уверены, что у вас не может быть x IS NULL ИЛИ x МЕЖДУ 4 И 40
jgmjgm 09
И это не дает такого разрешения, как если бы оно было беззнаковым int. Значение unsigned int может 2^32-1возрасти до , а подписанные int могут подняться до 2^31-1.
JukesOnYou
2
NULLи CHECKполностью ортогональны. Вы можете иметь NULL/ NOT NULLстолбцы с или без CHECK. Просто обратите внимание, что, согласно документации на postgresql.org/docs/9.4/ddl-constraints.html , CHECKвозврат NULL оценивается как TRUE, поэтому, если вы действительно хотите предотвратить NULL, используйте NOT NULLвместо него (или в дополнение к CHECK).
flaviovs
использование CHECK не позволяет мне хранить адреса ipv4 integer(по крайней мере, без случайного положительного или отрицательного значения ...)
Хансенрик
5

Разговор о DOMAINS интересен, но не имеет отношения к единственно возможному источнику этого вопроса. Желание беззнаковых целых чисел состоит в том, чтобы удвоить диапазон целых чисел с тем же количеством битов, это аргумент эффективности, а не желание исключить отрицательные числа, каждый знает, как добавить ограничение проверки.

Когда кто-то спросил об этом , Том Лейн сказал:

По сути, это нулевой шанс, что это произойдет, если вы не найдете способ вписать их в числовую иерархию продвижения, которая не нарушила бы многие существующие приложения. Мы смотрели на это не раз, если мне не изменяет память, и не смогли придумать работоспособный дизайн, который, похоже, не нарушал бы POLA.

Что такое «ПОЛА»? Google дал мне 10 результатов, которые не имеют смысла . Не уверен, что это политически некорректная мысль и поэтому подвергается цензуре. Почему этот поисковый запрос не даст никакого результата? Без разницы.

Вы можете без особых проблем реализовать беззнаковые целые числа как типы расширения. Если вы сделаете это с помощью C-функций, то вообще не будет никаких штрафов за производительность. Вам не нужно расширять синтаксический анализатор для работы с литералами, потому что PgSQL имеет такой простой способ интерпретировать строки как литералы, просто напишите '4294966272' :: uint4 в качестве литералов. Броски тоже не должны иметь большого значения. Вам даже не нужно делать исключения диапазона, вы можете просто обработать семантику '4294966273' :: uint4 :: int как -1024. Или можете выдать ошибку.

Если бы я этого хотел, я бы это сделал. Но поскольку я использую Java на другой стороне SQL, для меня это не имеет большого значения, поскольку в Java нет этих целых чисел без знака. Так что я ничего не получаю. Меня уже раздражает, если я получаю BigInteger из столбца bigint, когда он должен уместиться в long.

Другое дело, если бы мне действительно нужно было хранить 32-битные или 64-битные типы, я мог бы использовать PostgreSQL int4 или int8 соответственно, просто помня, что естественный порядок или арифметика не будут работать надежно. Но на хранение и извлечение это не влияет.


Вот как я могу реализовать простой беззнаковый int8:

Сначала я буду использовать

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
)

минимальные 2 функции, uint8_inи uint8_outя должен сначала определить.

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;

необходимо реализовать это в C uint8_funcs.c. Итак, я использую сложный пример отсюда и упрощаю его:

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

ну ладно, или ты можешь просто найти это уже готовым .

Гюнтер Шадов
источник
1
Полагаю, POLA - это «принцип наименьшего удивления». Это предполагает, что изменение может неожиданным образом изменить существующее поведение.
Доктор Эвал
1

Согласно последней документации, единственное целое число поддерживается, но в таблице нет целого числа без знака. Однако серийный тип похож на беззнаковый, за исключением того, что он начинается с 1, а не с нуля. Но верхняя граница такая же, как у опаленной. Так что у системы действительно нет поддержки без подписи. Как указал Питер, открыта дверь для реализации неподписанной версии. Код, возможно, придется много обновлять, просто слишком много работы из моего опыта работы с программированием на 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 имеет беззнаковое целое число типа, которое неведомо многим: OID.

oidТипа в настоящее время осуществляется как беззнаковое четыре-байтовое целое число. [...]

Сам oidтип имеет несколько операций вне всякого сравнения. Однако его можно преобразовать в целое число, а затем обработать с помощью стандартных целочисленных операторов. (Остерегайтесь возможной путаницы между подписанными и неподписанными, если вы это сделаете.)

Однако это не числовой тип , и попытка выполнить с ним какие-либо арифметические операции (или даже побитовые операции) потерпит неудачу. Кроме того, это всего лишь 4 байта ( INTEGER), нет соответствующего 8-байтового ( BIGINT) беззнакового типа.

Так что использовать это самостоятельно не очень хорошая идея, и я согласен со всеми другими ответами, что в дизайне базы данных Postgresql вы всегда должны использовать столбец INTEGERили BIGINTдля своего серийного первичного ключа - чтобы он начинался с отрицательного ( MINVALUE) или разрешал его to wrap around ( CYCLE), если вы хотите исчерпать весь домен.

Однако он очень полезен для преобразования ввода / вывода, например, при переходе с другой СУБД. Вставка значения 2147483648в целочисленный столбец приведет к ошибке « ОШИБКА: целое число вне допустимого диапазона », в то время как использование выражения 2147483648::OIDработает нормально.
Точно так же при выборе целочисленного столбца в качестве текста с помощью mycolumn::TEXTвы в какой-то момент получите отрицательные значения, но с mycolumn::OID::TEXTвы всегда получите натуральное число.

См. Пример на dbfiddle.uk .

Берги
источник
Если вам не нужны операции, то единственное значение использования OID - это то, что ваш порядок сортировки работает. Если это то, что вам нужно, хорошо. Но скоро кому-то понадобится uint8, и тогда они тоже пропадут. Суть в том, что для хранения 32-битных или 64-битных значений вы можете просто использовать int4 и int8 соответственно, просто нужно быть осторожным с операциями. Но написать расширение легко.
Гюнтер Шадов