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

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

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

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

Спасибо.


person Adrian Hoe    schedule 28.12.2013    source источник
comment
Пока нет, но скоро, и мы подумываем о переходе на PostgreSQL.   -  person Adrian Hoe    schedule 28.12.2013
comment
Я не думаю, что это лучшее место, чтобы спрашивать, почему были приняты те или иные решения, более подходящим может быть один из списков рассылки PostgreSQL. Если вам нужны автоматически увеличивающиеся значения, используйте serial (от 1 до 2 147483647) или bigserial (от 1 до 9 223372036854775807). 64-битное целое число со знаком, вероятно, предлагает более чем достаточно места.   -  person mu is too short    schedule 28.12.2013
comment
Спасибо @muistooshort. Это ответило на главный вопрос. Но как насчет беззнакового целочисленного типа, который не увеличивается автоматически и не является первичным ключом? У меня есть столбцы, в которых хранится целое число без знака в диапазоне от 0 до 2 ^ 32.   -  person Adrian Hoe    schedule 28.12.2013
comment
Краткий обзор документации PostgreSQL (postgresql.org/docs/current/interactive/ index.html) может помочь вам лучше понять, на что способен PostgreSQL. Единственная причина, по которой я бы использовал MySQL в эти дни, заключается в том, что я уже много вложил в нее: PostgreSQL быстр, загружен полезными функциями и создан людьми, которые довольно параноидально относятся к своим данным. имхо конечно :)   -  person mu is too short    schedule 28.12.2013
comment
Еще раз спасибо @muistooshort за подсказки.   -  person Adrian Hoe    schedule 28.12.2013


Ответы (6)


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

http://www.postgresql.org/docs/9.4/static/sql-createddomain.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=# select (346346 :: uint2);

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

person Karl Tarbe    schedule 05.08.2015
comment
Но я предполагаю, что использование этого домена каждый раз, когда нам нужен столбец без знака, будет иметь накладные расходы на INSERT/UPDATE. Лучше использовать это там, где это действительно необходимо (что бывает редко), и просто привыкнуть к мысли, что тип данных не устанавливает нижний предел, который мы желаем. В конце концов, это еще и верхний предел, обычно бессмысленный с логической точки зрения. Числовые типы не предназначены для обеспечения соблюдения ограничений наших приложений. - person Federico Razzoli; 30.04.2018
comment
Единственная проблема с этим подходом заключается в том, что вы тратите впустую 15 бит хранилища данных, которые не используются. Не говоря уже о том, что проверка также стоит небольшого количества эффективности. Лучшим решением было бы добавление Postgres unsigned в качестве типа первого класса. В таблице с 20 миллионами записей с таким индексированным полем вы тратите 40 МБ места на неиспользуемые биты. Если вы злоупотребляете этим за другими 20 столами, вы теряете 800 МБ места. - person tpartee; 19.03.2020

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

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

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

person Peter Eisentraut    schedule 31.12.2013
comment
Этот вопрос достаточно популярен, поэтому я решил его исправить: github.com/petere/pguint - person Peter Eisentraut; 09.02.2015
comment
Однако было бы очень полезно иметь преобразования ввода/вывода для целочисленных литералов без знака. Или даже просто шаблон to_char. - person Bergi; 21.09.2019

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

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

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

person TriAnMan    schedule 11.11.2014
comment
Стоит отметить, что в столбцах, использующих CHECK, не может быть NULL. - person Minutis; 20.09.2017
comment
@Minutis, вы уверены, что у вас не может быть x IS NULL OR x BETWEEN 4 AND 40 - person jgmjgm; 09.03.2018
comment
И это не дает вам такого же разрешения, как если бы оно было беззнаковым целым. Это означает, что беззнаковое целое число может достигать 2^32-1, а подписанное целое число может достигать 2^31-1. - person JukesOnYou; 02.05.2018
comment
NULL и CHECK полностью ортогональны. У вас может быть NULL/NOT NULL столбцов с CHECK или без них. Просто обратите внимание, что согласно документации на postgresql.org/docs/9.4/ddl -constraints.html, CHECK, возвращающий NULL, оценивается как TRUE, поэтому, если вы действительно хотите предотвратить NULL, используйте вместо него NOT NULL (или в дополнение к CHECK). - person flaviovs; 28.03.2019
comment
использование CHECK не позволяет мне хранить адреса ipv4 в integer (по крайней мере, не без того, чтобы они случайным образом становились положительными или отрицательными, по крайней мере ..) - person hanshenrik; 26.05.2020

Разговор о ДОМЕНАХ интересен, но не имеет отношения к единственно возможному происхождению этого вопроса. Стремление к беззнаковым целым числам состоит в том, чтобы удвоить диапазон целых чисел с тем же количеством битов, это аргумент эффективности, а не желание исключить отрицательные числа, все знают, как добавить контрольное ограничение.

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

По сути, вероятность того, что это произойдет, равна нулю, если только вы не найдете способ вписать их в числовую иерархию продвижения, которая не нарушила бы многие существующие приложения. Мы смотрели на это не раз, если память не изменяет, и не смогли придумать работоспособный дизайн, который, казалось бы, не нарушал 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);
}

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

person Gunther Schadow    schedule 18.01.2020
comment
Я предполагаю, что POLA — это принцип наименьшего удивления. Это предполагает, что изменение может изменить существующее поведение неожиданным образом. - person Doctor Eval; 22.02.2020
comment
Потребовалось 4 года, чтобы найти правильный ответ на этот вопрос. Удивительный... - person rdnobrega; 18.12.2020

Согласно последней документации, целое число со знаком поддерживается, но целое число без знака в таблице отсутствует. Однако последовательный тип похож на беззнаковый, за исключением того, что он начинается с 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
person Kemin Zhou    schedule 17.01.2019

В Postgres есть целочисленный тип без знака, о котором многие не знают: OID< /сильный>.

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

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

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

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

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

См. пример на сайте dbfiddle.uk.

person Bergi    schedule 21.09.2019
comment
Если вам не нужны операции, то единственная польза от использования OID заключается в том, что ваш порядок сортировки работает. Если это то, что вам нужно, хорошо. Но скоро кому-то захочется uint8 и тогда они тоже пропадут. Суть в том, что для хранения 32-битных или 64-битных значений вы можете просто использовать int4 и int8 соответственно, просто нужно быть осторожным с операциями. Но легко написать расширение. - person Gunther Schadow; 18.01.2020