【问题标题】:Postgresql update column of numeric type with NULL value fails if all value of this column is NULL如果该列的所有值为 NULL,则具有 NULL 值的数字类型的 Postgresql 更新列将失败
【发布时间】:2021-09-24 19:34:25
【问题描述】:

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

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

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

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,查询就会成功。这是为什么呢?

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

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

非常感谢!

更新

来自@Marth 和@Gordon Linoff 的每个cmets,当我使用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 值时,postgresql 无法推断列的类型,所以它与 text 一起使用(尽管还有一个 @987654336 @ 伪类型,但它必须用于其他用途)。您可以将强制转换添加到第一行(好吧,所有行也可以,但只需第一行就足够了),例如 FROM (VALUES (1, NULL::numeric, 1), ...) 来指示列的类型。
  • 非常感谢您的回复。由于我使用的是 psycopg2,因此我无法在 VALUES 部分中添加类型,但可以在 SET 部分中添加它。请查看我的更新。

标签: postgresql psycopg2 pgadmin-4


【解决方案1】:

这是由于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 是一个 dbfiddle,它说明了其中的一部分。

【讨论】:

  • 非常感谢您的详细解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-16
  • 2020-06-05
相关资源
最近更新 更多