【问题标题】:Postgres: getting "... is out of range for type integer" when using NULLIFPostgres:使用 NULLIF 时获取“...超出整数类型的范围”
【发布时间】:2022-01-04 22:41:36
【问题描述】:

就上下文而言,这个问题发生在我使用默认 postgres 数据库驱动程序编写的 Go 程序中。

我一直在构建一个服务来与一个 postgres 数据库对话,该数据库有一个类似于下面列出的表:

CREATE TABLE object (
    id SERIAL PRIMARY KEY NOT NULL,
    name VARCHAR(255) UNIQUE,
    some_other_id BIGINT UNIQUE
    ...
);

我已经为这个项目创建了一些端点,包括一个“安装”端点,它可以像这样有效地充当一个 upsert 函数:

INSERT INTO object (name, some_other_id)
VALUES ($1, $2)
ON CONFLICT name DO UPDATE SET
    some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id)

我还有一个“更新”端点,其底层查询如下:

UPDATE object
SET some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id)
WHERE name = $1

问题:

每当我运行更新查询时,我总是遇到错误,引用字段“some_other_id”:

pq:值“1010101010144”超出整数类型的范围

但是,即使该行已经存在于数据库中(当它必须评估 COALESCE 语句时),查询的“upsert”版本也不会出现此错误。我已经能够通过将 COALESCE 语句更新为如下方式来防止此错误:

COALESCE(NULLIF($2, CAST(0 AS BIGINT)), object.some_other_id)

但由于第一次查询从未发生过这种情况,我想知道这种不一致是因为我做错了什么还是我不明白的事情?还有最佳实践是什么,我应该转换所有值吗?

我肯定将一个 64 位整数传递给“some_other_id”的查询,即使没有显式类型转换,第一个查询也适用于 Go 实现。

如果需要更多信息(或 Go 实现),请告诉我,非常感谢! (:

编辑:

为了消除混淆,查询直接在 Go 代码中执行,如下所示:

res, err := s.db.ExecContext(ctx, `UPDATE object SET some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id) WHERE name = $1`,
    "a name",
    1010101010144,
)

两个查询的执行方式完全相同。

编辑:还在我当前的解决方法中更正了参数(从 $51$2)。

我还想借此机会指出,该查询确实适用于我提出的修复,这表明问题在于我将 postgres 与 NULLIF 语句中的类型混淆了?在我的代码和数据库之间没有存储过程要求INTEGER arg,至少我已经写过。

【问题讨论】:

  • 试试... NULLIF(EXCLUDED.some_other_id, 0) ...。也许您不允许放置两个对$2 的引用?请参阅 documentation of INSERT 了解特殊的“EXCLUDED”表。
  • “但是,即使该行已经存在于数据库中(当它必须评估 COALESCE 语句时),查询的“upsert”版本也不会出现此错误。” i> -- 在评估COALESCE(NULLIF 的情况下,$2object.some_other_id 的值是否大于一个可以放入int4
  • 如果还不清楚,这与 Go 无关,与 postgres 如何推断参数占位符的类型有关。我的假设是您的INSERT 查询不会失败,因为从(name,some_other_id) VALUES ($1,$2) 可以清楚地看出$2 应该与目标some_other_id 列具有相同的类型,即int8。然后,此类型信息也用于查询的DO UPDATE SET 部分的NULLIF 表达式。您可以通过在插入中使用(name) VALUES ($1) 来测试此假设。
  • 您会发现DO UPDATE SET 中的NULLIF 会以与UPDATE 查询中相同的方式失败。并且UPDATE 查询失败,因为没有可用于推断$2 类型的direct 目标列。而是使用NULLIF 表达式,特别是第二个参数,即0,其类型为int4,用于推断第一个参数的类型,即$2
  • 所以...不,您不会混淆 postgres,而且您的查询对参数类型的表达不够。为避免此问题,您应在无法准确推断类型的情况下使用带有参数占位符的显式类型转换。即使用NULLIF($2::int8, 0)

标签: postgresql go sql-update sql-insert


【解决方案1】:

这与 postgres 解析器如何解析参数类型有关。我不知道它是如何实现的,但鉴于观察到的行为,我会假设 INSERT 查询不会失败,因为从 (name,some_other_id) VALUES ($1,$2) 可以清楚地看出 $2 参数应该与目标some_other_id 列,其类型为int8。然后,此类型信息也用于查询的 DO UPDATE SET 部分的 NULLIF 表达式。

您还可以通过在INSERT 中使用(name) VALUES ($1) 来测试此假设,您将看到DO UPDATE SET 中的NULLIF 表达式随后会以与UPDATE 查询中相同的方式失败。

所以UPDATE 查询失败,因为没有足够的上下文供解析器推断$2 参数的准确类型。解析器可以用来推断$2类型的“最接近”的东西是NULLIF调用表达式,特别是它使用调用表达式的第二个参数的类型,即0,它的类型是@ 987654338@,然后它将该类型信息用于第一个参数,即$2

为避免此问题,您应该对无法准确推断类型的任何参数使用显式类型转换。即使用NULLIF($2::int8, 0)

【讨论】:

    【解决方案2】:
    COALESCE(NULLIF($51, CAST(0 AS BIGINT)), object.some_other_id)
    

    51 岁?真的吗?

    pq:值“1010101010144”超出整数类型的范围

    注意,错误信息中的数据类型是integer,而不是bigint

    我认为错误的原因是没有显示代码于是我拿出一个魔法水晶球,用手传球

    一个“安装”端点,它可以像这样有效地充当一个 upsert 函数

    我还有一个“更新”端点

    您是否将端点称为PostgreSQL function(存储过程)?我想是的。 同样 $1, $2 看起来像 PostgreSQL 函数参数。

    魔法水晶球说:你有两个不同数据类型参数的PostgreSQL函数:

    1. “安装”端点具有 $2 函数参数作为 bigint 数据类型。好像是CREATE FUNCTION Install(VARCHAR(255), bigint)

    2. “更新”端点将 $2 函数参数作为 integer 数据类型,而不是 bigint。看起来像CREATE FUNCTION Update(VARCHAR(255), integer)

    最后,我会重写你的条件更容易理解:

    UPDATE object
    SET some_other_id = 
    CASE 
    WHEN $2 = 0 THEN object.some_other_id
    ELSE $2
    END
    WHERE name = $1
    

    【讨论】:

    • 不,很抱歉造成混乱。端点是我提到的 Go 程序的一部分,它是一个 REST API 端点,它接受 JSON 输入并将对象写入数据库。没有 SQL 函数,我没有将端点误认为存储过程。上面的查询和我的 Go 代码中写的完全一样,我会尽快更新这个问题。
    • 也许在“更新”端点使用整数数据类型但在“安装”端点使用 bigint 数据类型的 Go 代码中?
    • 我想知道它是否可能是这样的(如果我理解正确的话)。我已经尝试在将值作为 args 放入查询时显式键入/转换值,并仔细检查查询是否采用相同的输入但没有这样的运气):另外感谢您在这方面的时间! (:
    • >我已经尝试显式输入/转换我认为寻找端点声明的值。
    猜你喜欢
    • 1970-01-01
    • 2020-03-22
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多