Update column values sequentially where the new value is based on updated values in a 'previous' row

时间:2019-05-18 00:27:01

标签: sql postgresql window-functions postgresql-9.6

Consider the following table:

select id, val, newval from test1;
 id | val | newval
----+-----+--------
  1 |   1 |      0
  2 |   2 |      0
  3 |   5 |      0
  4 |   9 |      0
  5 |  10 |      0

I am looking for an update query that can sequentially update the values in the newval column where the updated value is the difference between val and val.prev (the value of val in the previous row) plus the UDATED value of newval from the previous row. The result would be:

select id, val, newval from test1;
 id | val | newval
----+-----+--------
  1 |   1 |      0 = set to zero since result would be NULL (no previous row exists)
  2 |   2 |      1 = 2  - 1 + 0
  3 |   5 |      4 = 5  - 2 + 1
  4 |   9 |      8 = 9  - 5 + 4
  5 |  10 |      9 = 10 - 9 + 8
                              ^ - uses updated value of newval

I came close to a solution with the following:

WITH tt AS (
    SELECT id, COALESCE(val - lag(val) OVER w + lag(newval) OVER w,0) as nv 
    FROM test1
    WINDOW w AS (ORDER BY id)
    )
UPDATE test1 SET newval = tt.nv FROM tt
WHERE test1.id = tt.id;

Which gives the following result:

select id, val, newval from test1;
 id | val | newval
----+-----+--------
  1 |   1 |      0
  2 |   2 |      1 = 2  - 1 + 0
  3 |   5 |      3 = 5  - 2 + 0
  4 |   9 |      4 = 9  - 5 + 0
  5 |  10 |      1 = 10 - 9 + 0
                              ^ - uses old value of newval

but this solution does not use the updated values of newval. It uses the old values.

I know I can write a function to loop through the table one row at a time but from what I have read this method would be inefficient and normally discouraged. My actual application is more complicated and involves large tables so efficiency is important.

1 个答案:

答案 0 :(得分:2)

我很确定您的逻辑可以简化为:

select id, val, (val - 1) as newval
from t;

为什么?您正在上一行中获取valval之间的差异的累积和。累积差最终是最新值减去第一个值。

第一个值为1。上面将这个值硬编码。调整第一个值的逻辑就很容易了:

select id, val,
       (val - first_value(val) over (order by id) as newval
from t;