【发布时间】: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的情况下,$2或object.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