【问题标题】:How can I improve this conditional UPDATE query?如何改进这个条件 UPDATE 查询?
【发布时间】:2018-07-27 04:25:23
【问题描述】:

我有一个表t 有几列,我们将它们命名为abc。我还有一个 state 列,它指示当前状态。还有一个id 列。

我想编写以下查询:总是更新列a,但bc 仅当应用程序state 仍然等于数据库state。这里,state 列用于乐观锁定。

我将这个查询写成如下:

UPDATE t
SET    a = $a$,
       b = (CASE WHEN state = $state$ THEN $b$ ELSE b END),
       c = (CASE WHEN state = $state$ THEN $c$ ELSE c END)
WHERE  id = $id$ AND
       (
         a != $a$ OR
         b != (CASE WHEN state = $state$ THEN $b$ ELSE b END) OR
         c != (CASE WHEN state = $state$ THEN $c$ ELSE c END)
       )

这里,$id$, $a$, ... 是来自应用程序的输入变量。 WHERE 子句的第二部分是避免不会有效更新任何内容的更新。

此查询按预期工作,但非常笨拙。我多次重复相同的条件。我正在寻找一种以更优雅的方式重写此查询的方法。如果这是一个简单的SELECT 查询,我可以用LATERAL JOIN 做一些事情,但我看不到如何在这里应用它。

如何改进这个查询?

【问题讨论】:

  • 你能说明你想用 a 做什么吗? “始终更新 a 列”或“避免不会有效更新任何内容的更新。”?
  • a 应更新,无论state。列bc 应由state 保护。 WHERE 子句是为了防止 zero updates:当新行与当前行完全相同时,我不希望我的 MVCC 数据库创建新行。

标签: sql postgresql


【解决方案1】:

您需要的唯一过滤器是 ID = $id

case 语句说如果状态不匹配就不要在更新中改变它,所以你不需要过滤它。

编辑

where Id = $id and a !=$a
   Or (state = $state and (b !=b or c!= $c))

如果你做的不止这些,那么“总是更新一个”就不一定是真的。

第三次尝试检查 a 保持不变但 b 或 c 更新的可能性。

【讨论】:

    【解决方案2】:

    将查询一分为二:

    UPDATE t
    SET    a = $a$
    WHERE  id = $id$
    
    UPDATE t
    SET    b = $b$,
           c = $c$
    WHERE  id = $id$ AND
           state = $state$
    

    如果您需要原子性,请封装在事务中。

    【讨论】:

    • 作为记录,我不认为检查更新是否会改变任何东西有帮助。证明我错了,测量执行时间。
    • 是的,它有助于防止创建新的行版本(乘以“写乘法”)
    • 如果您有数百万行并且您只有效地更新了其中的一小部分,那么检查更新是否有效地做任何事情都会有很大帮助。如果有复制,情况会变得更糟,因为必须记录和复制每个更改。查询执行时间可能或多或少相同,但所有后台进程的总负载可能会有很大差异。视情况而定。
    • @schizofrenic_bit OK 是有道理的。
    【解决方案3】:

    这看起来有点干净(未经测试):


    WITH src AS (
            SELECT    $a$ AS a
           , (CASE WHEN state = $state$ THEN $b$ ELSE b END) AS b
           , (CASE WHEN state = $state$ THEN $c$ ELSE c END) AS c
            FROM t
            WHERE  id = $id$
            )
    UPDATE t dst
    SET a=src.a, b=src.b, c=src.c
    FROM src
    WHERE  dst.id = src.id
    AND   (src.a, src.b, src.c) IS DISTINCT FROM (dst.a, dst.b, dst.c)
            ;
    

    【讨论】:

    • 谢谢。这看起来更好,但仍然不是 100% 相信 :)。也许我是一个完美主义者。
    • 注意:... DISTINCT FROM ... 仅在您希望 NULL 比较不等于非 NULL 时才需要。否则,您可以使用现有的 (... OR ... OR...) 条件。
    【解决方案4】:

    编辑:我花了一段时间才意识到我的错:这个问题显然针对的是一次更新,而我的答案试图更新许多行。但是,如果您需要对一组行执行此更新,您可以:

    • 在临时表中插入需要的参数
    • 在“t2”子查询中加入该表
    • 选择它的列(例如 tempTable.b As tempB)
    • 替换参数(例如 $b$ -> t2.tempB)

    .

    UPDATE t
    SET a=source.a,
        b=source.b,
        c=source.c
    FROM 
        (
            SELECT 
                id,
                a, 
                (CASE WHEN UpdateCondition THEN $b$ ELSE b END) AS b,
                (CASE WHEN UpdateCondition THEN $c$ ELSE c END) AS c
            FROM 
                (  
                    SELECT state = $state$ As UpdateCondition, * FROM t
                ) As t2
            WHERE 
                id = $id$ AND
                (
                    a != $a$ OR
                    b != (CASE WHEN UpdateCondition THEN $b$ ELSE b END) OR
                    c != (CASE WHEN UpdateCondition THEN $c$ ELSE c END)
                ) AS source
    WHERE t.id=source.id;
    

    t2 的子查询为您提供您的状态 Condition,并且每行只对其执行一次计算。

    “源”的子查询为您提供映射值并过滤未更改的值。

    【讨论】:

    猜你喜欢
    • 2013-07-21
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多