为什么需要将NULL强制转换为列类型?


10

我有一个帮助程序,它为我生成一些代码以进行批量更新并生成如下所示的SQL:

(活动字段和核心字段均为类型boolean

UPDATE fields as t set "active" = new_values."active","core" = new_values."core"
FROM (values 
(true,NULL,3419),
(false,NULL,3420)
) as new_values("active","core","id") WHERE new_values.id = t.id;

但是,它失败了:

ERROR: column "core" is of type boolean but expression is of type text

我可以通过添加::booleannull 来使其正常工作,但这似乎很奇怪,为什么将NULL视为类型TEXT

同样,转换起来有点棘手,因为它需要大量的代码重做才能知道它应该将NULL转换为哪种类型(列和值的列表当前是从一个简单的JSON对象数组自动生成的) 。

为什么这是必需的,并且有一个更优雅的解决方案,不需要生成的代码知道NULL的类型?

如果相关,我正在使用Node.JS上的续集来执行此操作,但在Postgres命令行客户端中也得到了相同的结果。

Answers:


16

这是一个有趣的发现。通常,NULL没有假定的数据类型,如您在此处看到的:

SELECT pg_typeof(NULL);

 pg_typeof 
───────────
 unknown

VALUES表格出现在图片中时,这会改变:

SELECT pg_typeof(core) FROM (
    VALUES (NULL)
) new_values (core);

 pg_typeof 
───────────
 text

此行为是在源代码中在描述https://doxygen.postgresql.org/parse__coerce_8c.html#l01373

 /*
  * If all the inputs were UNKNOWN type --- ie, unknown-type literals ---
  * then resolve as type TEXT.  This situation comes up with constructs
  * like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); SELECT 'foo'
  * UNION SELECT 'bar'; It might seem desirable to leave the construct's
  * output type as UNKNOWN, but that really doesn't work, because we'd
  * probably end up needing a runtime coercion from UNKNOWN to something
  * else, and we usually won't have it.  We need to coerce the unknown
  * literals while they are still literals, so a decision has to be made
  * now.
  */

(是的,由于出色的注释,PostgreSQL源代码相对容易理解,并且在大多数地方都可以使用。)

但是,可能的解决方法如下。假设您一直在生成VALUES与给定表的所有列匹配的记录(其他情况请参见下面的第二个注释)。从您的示例中,一个小技巧可能会有所帮助:

SELECT (x).* FROM (VALUES ((TRUE, NULL, 1234)::fields)) t(x);

 active  core   id  
────────┼──────┼──────
 t             1234

在这里,您可以使用转换为表格类型的行表达式,然后将其提取回表格中。

基于以上所述,您UPDATE可能会像

UPDATE fields AS t set active = (x).active, core = (x).core
FROM ( VALUES
           ((true, NULL, 3419)::fields),
           ((false, NULL, 3420)::fields)
     ) AS new_values(x) WHERE (x).id = t.id;

笔记:

  • 我删除了双引号,以提高人类可读性,但是可以保留它们,因为它们在生成(列)名称时会有所帮助。
  • 如果仅需要列的子集,则可以为此创建自定义类型。以与上面相同的方式使用它们(在这里,我使用由表自动创建的类型,并保留后者的行结构)。

看一下dbfiddle上的全部工作。


谢谢,这很有趣,但是,对我来说,上面的代码产生了Cannot cast type boolean to bigint in column 1(错误点在第一个字段语句之间的::上)
ChristopherJ

1
@ChristopherJ答案假定被调用的表fields具有3列,(active, core, id)具有布尔,布尔和int / bigint类型。您的表中是否有更多的列或不同的类型,或者按不同的顺序定义了列?
ypercubeᵀᴹ

嗯,我知道了,谢谢,是的,还有更多的列,并且顺序不同。得到了谢谢
ChristopherJ'Jan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.