ChatGPT解决这个技术问题 Extra ChatGPT

为什么无符号整数在 PostgreSQL 中不可用?

我看到这篇文章 (What is the difference between tinyint, smallint, mediumint, bigint and int in MySQL?) 并意识到 PostgreSQL 不支持无符号整数。

谁能帮忙解释为什么会这样?

大多数时候,我使用无符号整数作为 MySQL 中的自动递增主键。在这样的设计中,当我将数据库从 MySQL 移植到 PostgreSQL 时,我该如何克服这个问题?

谢谢。

还没有,但很快,我们正在考虑迁移到 PostgreSQL。
我不认为这是询问为什么做出某些决定的最佳地点,PostgreSQL 邮件列表之一可能更合适。如果您想要自动递增值,请使用 serial(1 到 2147483647)或 bigserial(1 到 9223372036854775807)。一个有符号的 64 位整数可能提供了足够多的空间。
谢谢@muistooshort。这回答了主要的关键问题。但是,一个既不是自动递增也不是主键的无符号整数类型呢?我确实有存储无符号整数的列,其范围从 0 到 2^32。
快速浏览 PostgreSQL 文档 (postgresql.org/docs/current/interactive/index.html) 可能有助于您更好地了解 PostgreSQL 的功能。这些天我使用 MySQL 的唯一原因是如果我已经对它进行了大量投资:PostgreSQL 速度快,加载了有用的功能,并且由对他们的数据非常偏执的人构建。国际海事组织当然:)
再次感谢@muistooshort 的指点。

P
Peter Eisentraut

它不在 SQL 标准中,因此实施它的一般冲动较低。

拥有太多不同的整数类型会使类型解析系统更加脆弱,因此在混合中添加更多类型存在一些阻力。

也就是说,没有理由不能做到。这只是很多工作。


这个问题很受欢迎,我已经着手解决它:github.com/petere/pguint
不过,对无符号整数文字进行输入/输出转换将非常有用。甚至只是一个 to_char 模式。
这是否也解释了为什么我们在 postgres 中没有 tinyint? (假设我们知道该值适合该范围,有时可能会更有效)
C
Community

已经回答了为什么 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”


但我想每次我们想要一个无符号列时使用这个域都会在插入/更新上产生开销。最好在真正需要的地方使用它(这种情况很少见),并且习惯于数据类型没有设置我们想要的下限的想法。毕竟,它也设置了一个上限,从逻辑的角度来看,这通常是没有意义的。数字类型不是为了强制我们的应用程序约束而设计的。
这种方法的唯一问题是您“浪费”了 15 位未使用的数据存储。更不用说检查还花费了一些效率。更好的解决方案是 Postgres 添加 unsigned 作为第一类类型。在一个有 2000 万条记录的表中,有这样的索引字段,您在未使用的位上浪费了 40MB 的空间。如果您在另外 20 个表中滥用它,那么您现在正在浪费 800MB 的空间。
K
K-Gun

您可以使用 CHECK 约束,例如:

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

此外,PostgreSQL 有 serialsmallserialbigserial 类型用于自动增量。


值得一提的是,在使用 CHECK 的列中不能有任何 NULL。
@Minutis 你确定你不能有 x IS NULL OR x BETWEEN 4 AND 40
如果它是无符号整数,这不会给你相同的分辨率。这意味着 unsigned int 可以上升到 2^32-1,同时有符号 int 可以上升到 2^31-1
NULLCHECK 完全正交。您可以拥有带有或不带有 CHECKNULL/NOT NULL 列。请注意,根据 postgresql.org/docs/9.4/ddl-constraints.html 中的文档,返回 NULL 的 CHECK 计算结果为 TRUE,因此如果您真的想防止 NULL,请改用 NOT NULL(或除了 CHECK)。
使用 CHECK 不允许我将 ipv4 地址存储在 integer 中(至少要让它们随机正或负,至少......)
G
Gunther Schadow

关于域的讨论很有趣,但与该问题的唯一可能来源无关。对无符号整数的期望是将具有相同位数的整数范围加倍,这是一个效率参数,而不是排除负数的愿望,每个人都知道如何添加检查约束。

asked by someone about it时,Tome Lane 说:

基本上,这种情况发生的可能性为零,除非您能找到一种方法将它们放入不会破坏许多现有应用程序的数字提升层次结构中。如果没记错的话,我们已经不止一次地研究过这个问题,但未能提出一个似乎不违反 POLA 的可行设计。

什么是“POLA”? Google gave me 10 results that are meaningless。不确定这是否是政治不正确的想法,因此受到审查。为什么这个搜索词不会产生任何结果?任何。

您可以将无符号整数实现为扩展类型,而不会有太多麻烦。如果您使用 C 函数执行此操作,那么根本不会有性能损失。您不需要扩展解析器来处理文字,因为 PgSQL 有一种将字符串解释为文字的简单方法,只需编写 '4294966272'::uint4 作为文字。演员阵容也不应该是一个大问题。您甚至不需要进行范围异常,您只需将 '4294966273'::uint4::int 的语义视为 -1024。或者你可以抛出一个错误。

如果我想要这个,我会做到的。但是由于我在 SQL 的另一端使用 Java,对我来说它没有什么价值,因为 Java 也没有那些无符号整数。所以我一无所获。如果我从一个 bigint 列中得到一个 BigInteger,我已经很恼火了,而它应该适合 long。

另一件事,如果我确实需要存储 32 位或 64 位类型,我可以分别使用 PostgreSQL int4 或 int8,只要记住自然顺序或算术不会可靠地工作。但是存储和检索不受此影响。

以下是我如何实现一个简单的 unsigned 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_inuint8_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 中实现它。所以我去使用 the complex example from here 并使其变得简单:

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

嗯,或者你可以just find it done already


我猜 POLA 是“最小惊讶原则”。它表明这种变化有可能以意想不到的方式改变现有行为。
M
Manngo

根据最新文档,支持有符号整数,但表中没有无符号整数。然而,串行类型有点类似于无符号,除了它从 1 开始而不是从 0 开始。但上限与签名相同。所以系统确实没有未签名的支持。正如彼得所指出的,实现未签名版本的大门是敞开的。代码可能需要更新很多,从我使用 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

B
Bergi

Postgres 确实有许多人不知道的无符号整数类型:OID

oid 类型当前实现为无符号四字节整数。 […] oid 类型本身几乎没有可比性的操作。但是,它可以转换为整数,然后使用标准整数运算符进行操作。 (如果您这样做,请注意可能的有符号与无符号混淆。)

但它不是 numeric type,尝试用它做任何算术(甚至按位运算)都会失败。而且,它只有 4 个字节(INTEGER),没有对应的 8 字节(BIGINT)无符号类型。

因此,自己使用它并不是一个好主意,我同意所有其他答案,即在 Postgresql 数据库设计中,您应该始终使用 INTEGERBIGINT 列作为 serial 主键 - 让它开始如果您想用尽整个域,则在否定 (MINVALUE) 中或允许它环绕 (CYCLE)。

但是,它对于输入/输出转换非常有用,例如从另一个 DBMS 迁移。将值2147483648插入整数列中,将导致“ 错误:整数超出范围” ,而使用表达式2147483648::OID则可以正常工作。
类似地,选择整数时列作为带有 mycolumn::TEXT 的文本,您将在某些时候获得负值,但对于 mycolumn::OID::TEXT,您将始终获得一个自然数。

请参阅an example at dbfiddle.uk


如果您不需要操作,那么使用 OID 的唯一价值是您的排序顺序有效。如果这是你需要的,那很好。但很快有人会想要一个 uint8 ,然后他们也迷路了。最重要的是,要存储 32 位或 64 位值,您可以分别使用 int4 和 int8,只需要小心操作。但是很容易写一个扩展。