【问题标题】:Postgres hangs on large table when trying to update multiple rows with a CASE statement尝试使用 CASE 语句更新多行时,Postgres 挂在大表上
【发布时间】:2013-09-18 19:52:07
【问题描述】:

我正在尝试使用 postgres 更新多行,我正在使用以下代码:

UPDATE foobar SET column_a = CASE
  WHEN column_b = '123' THEN 1
  WHEN column_b = '345' THEN 2
END;

如果我创建一个新表,它可以完美地工作,但是当在一个有 800 万行的大表上运行它时,它会无限期地挂起。我首先在 Adminer(网络界面)和控制台中尝试过。

但是这很好用:

UPDATE foobar SET column_a=1 WHERE column_b='123';

我对在我的代码中实施这种方法犹豫不决,因为我会同时进行数千次更新,并且更愿意将它们放在一个语句中。关于为什么第一个示例会挂起 postgres 而第二个示例可以正常工作的任何想法?我只是仔细检查了一下,我没有在桌子上应用任何规则。

【问题讨论】:

标签: sql postgresql sql-update case


【解决方案1】:

问题是..

声明:

CASE
  WHEN column_b = '123' THEN 1
  WHEN column_b = '345' THEN 2
END;

.. 只是以下简称:

CASE
  WHEN column_b = '123' THEN 1
  WHEN column_b = '345' THEN 2
  ELSE NULL
END

意思是,如果没有WHERE 子句,您的UPDATE 语句不仅仅是“尝试”,它实际上会更新表中的每一行,其中大部分为NULL
也许,列上的NOT NULL 约束防止了数据丢失......

更好的解决方案是..

我将一次有数千个更新,并且更愿意将它们放在一个语句中。

更快(并且更短)对于大型集:

UPDATE foobar f
SET    column_a = val.a
FROM  (
   VALUES
     (123, 1)
    ,(345, 2)
   ) val(b, a)
WHERE f.column_b = val.b

加入一个集合比遍历每一行的CASE 分支的长列表更容易。随着列表越长,差异会迅速扩大。

另外,无论哪种方式,请务必在column_b 上有一个索引

您可以将VALUES 表达式替换为产生适当行的任何表、视图或子选择。

注意:
我假设column_acolumn_b 的类型为integer。在这种情况下,您问题中 '123' 周围的单引号从来没有帮助过。您最好使用数字文字而不是字符串文字。 (即使它也适用于字符串文字。)

'123' 这样的字符串文字默认为unknown 类型。
123 这样的数字字面量默认为 integer - 如果数字太大,则默认为 bigint / numeric

如果您要处理非默认数据类型,则必须显式转换。看起来像:

...
FROM  (
   VALUES
     ('123'::sometype, '1'::sometype)  -- first row defines row type
    ,('345', '2')
   ) val(b, a)
...

【讨论】:

  • 感谢您的解释。
  • 我认为这是相关问题stackoverflow.com/questions/18797608/…
  • @ErwinBrandstetter 实际上我喜欢 update ... from solution 不仅是为了速度,而且因为它的概括性要好得多 - 如果您需要两列或更多列,您不必编写额外的案例,我在我的回答中提到过
  • @RomanPekar - 两者是相关的。我在另一个问题上得到的第一个解决方案不包括 where 语句。我很困惑,因为这是一个可行的解决方案并且得到了支持,我认为我做错了什么。直到我发布这个问题之后,MatheusOl 才纠正了这个问题,然后它也解决了这个问题。
【解决方案2】:

我会保留这个问题,以防有人遇到这个问题。

这个查询是罪魁祸首:

UPDATE foobar SET column_a = CASE
  WHEN column_b = '123' THEN 1
  WHEN column_b = '345' THEN 2
END;

问题是它缺少 WHERE 语句,因此它试图更新所有行。对于大型数据库,这可能是一个问题,就我而言,它只是超时了。一旦我在那里添加了 where 语句,它就解决了这个问题。

解决办法如下:

UPDATE foobar SET column_a = CASE
   WHEN column_b = '123' THEN 1
   WHEN column_b = '345' THEN 2
END
WHERE column_b IN ('123','345')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-05
    • 2014-08-13
    • 1970-01-01
    • 2015-02-27
    • 2023-03-08
    • 1970-01-01
    相关资源
    最近更新 更多