如果此列的所有值为 NULL,则具有 NULL 值的数字类型的 Postgresql 更新列失败

Postgresql update column of numeric type with NULL value fails if all value of this column is NULL

我有一个这样的数据库table:

idx[PK] a[numeric] b[numeric]
1 1 1
2 2 2
3 3 3
4 4 4
... ... ...

在 pgadmin4 中,我尝试用一​​些空值更新此 table,我注意到以下查询失败:

UPDATE test as t SET
a = e.a,b = e.b
FROM (VALUES (1,NULL,NULL),(2,NULL,NULL),(3,NULL,NULL)) 
AS e(idx, a, b)
WHERE t.idx = e.idx 
UPDATE test as t SET
a = e.a,b = e.b
FROM (VALUES (1,NULL,1),(2,NULL,2),(3,NULL,NULL)) 
AS e(idx, a, b)
WHERE t.idx = e.idx 

报错信息是这样的:

ERROR:  column "a" is of type numeric but expression is of type text
LINE 2: a = e.a,b = e.b
            ^
HINT:  You will need to rewrite or cast the expression.
SQL state: 42804
Character: 43

但是,这会成功:

UPDATE test as t SET
a = e.a,b = e.b
FROM (VALUES (1,NULL,1),(2,2,NULL),(3,NULL,NULL)) 
AS e(idx, a, b)
WHERE t.idx = e.idx 

似乎如果我正在更新的其中一列的新值都是 NULL,则查询失败。但是,只要至少有一个值是数字而不是 NULL,查询就会成功。这是为什么?

我确实在这里简化了我的真实案例,因为我的实际 table 有数百万行和超过 10 列。使用 Python 和 psycopg2,当我尝试在一个查询中更新 50,000 行时,即使列中的值不是 NULL,之前的错误仍然会出现。我猜这是因为系统扫描一定数量的行来决定类型是否正确,而不是所有 50,000 行。

因此,如何在我的现实世界中避免这种失败?有没有更好的查询来代替 UPDATE?

非常感谢!

更新

根据@Marth 和@Gordon Linoff 的评论,由于我使用的是 psycopg2,所以我在代码中执行了以下操作:

from psycopg2.extras import execute_values
sql = """UPDATE test as t SET
a = (e.a::numeric),
b = (e.b::numeric)
FROM (VALUES %s) 
AS e(idx, a, b)
WHERE t.idx = e.idx"""
execute_values(cursor, sql, data)

cursor来自数据库连接。 data 是我的值 (idx, a, b) 形式的元组列表。

这是由于 NULL 在这些情况下的工作方式的默认行为。 NULL 通常是未知类型,然后将其视为任何必要的类型。

values() 语句中,Postgres 尝试破译类型。它像处理 union 一样处理单个记录。但如果都是 NULL 。 . .好吧,那就没有信息了。 Postgres 决定使用 text 作为通用默认值。

同样重要的是要了解这失败并出现相同的错误:

UPDATE test t
    SET a = ''
    WHERE t.id = 1;

问题在于 Postgres 不会将空字符串转换为数字(与其他一些数据库不同)。

无论如何,这很容易通过将 NULL 转换为适当的类型来解决:

UPDATE test t
    SET a = e.a,b = e.b
    FROM (VALUES (1, NULL::numeric, NULL::numeric),
                 (2, NULL, NULL),
                 (3, NULL, NULL)
         ) e(idx, a, b)
WHERE t.idx = e.idx ;

您可以明确表示所有出现的 NULL,但这不是必需的。

Here 是一个 db<>fiddle 说明了其中的一些。