尝试使用CASE语句更新多行时,Postgres会挂在大表上

时间:2013-09-14 03:18:31

标签: sql postgresql sql-update case

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

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

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

然而这很好用:

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

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

2 个答案:

答案 0 :(得分:5)

问题是......

声明:

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)
...

答案 1 :(得分: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')