使用以前的值更新列

UPDATE column with previous value

想象一个样本 table

CREATE TABLE mytable (myid integer, myval double precision);

如果 myval 等于 0,我想将 myval 更新为之前的非零值,按 myid 排序。

为了使其正常工作,查询必须从最低的 myid 开始更新并以最高的结束。

这次真不知道从何说起。下面说 window functions are not allowed in UPDATE:

UPDATE mytable
SET myval = LAG(myval) OVER (ORDER BY myid)
WHERE myval = 0
RETURNING *;

使用 FROM 子查询的更复杂的替代方案以语法错误或哑输出结束,因为子查询被评估一次而不是每行一次。最后这句话让我想到了 SELECT ... LEFT JOIN LATERAL ... 结构,但我无法让它与更新语句一起工作。

解决方案的第一部分:因为您不能在 UPDATESET 子句中调用 window 函数,您可以使用 cte 代替:

WITH list AS
(
  SELECT myval, LAG(myval) OVER (ORDER BY myid) AS new_val
    FROM mytable
   WHERE myval = 0
)
UPDATE mytable AS t
SET myval = l.new_val
FROM list AS l
WHERE t.myval = l.myval ;

解决方案的第二部分:为了按指定顺序用非零值替换零值,我们不能添加子句FILTER (WHERE myval <> 0)lag() 函数,因为该子句仅特定于聚合函数(通过在后面添加 OVER() 子句,所有聚合函数都可以用作 window 函数,但“纯” window 函数不是聚合函数)。所以这里我们可以定义自己的聚合函数replaced_by_first_previous_non_zero_value()如下:

CREATE OR REPLACE FUNCTION replaced_by_first_previous_non_zero_value(x double precision, y double precision)
RETURNS double precision LANGUAGE sql AS
$$
SELECT CASE 
         WHEN y = 0
         THEN COALESCE(x, y)
         ELSE y
       END ;
$$ ;

DROP AGGREGATE IF EXISTS replaced_by_first_previous_non_zero_value(double precision) ;
CREATE AGGREGATE replaced_by_first_previous_non_zero_value(double precision)
( sfunc = replaced_by_first_previous_non_zero_value
, stype = double precision
) ;

然后,对于以下查询:

SELECT myval, replaced_by_first_previous_non_zero_value(myval) OVER (ORDER BY myid RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
  FROM unnest(array[5, 0, 0 , 0 , 6] :: double precision[]) with ordinality as a(myval, myid)

我们得到以下结果:

myval | replaced_by_first_previous_non_zero_value
5     | 5
0     | 5
0     | 5
0     | 5
6     | 6
update mytable t set myval=(select s.myval from mytable s where s.myid < t.myid and s.myval!=0 order by s.myid desc limit 1) where t.myid in (select myid from mytable where myval=0 order by myid for update) ;

结果

select * from mytable;
 myid | myval
------+-------
    1 |     1
    2 | 0.123
    3 |     0
    4 |     5
    7 |     0

update mytable t set myval=(select s.myval from mytable s where s.myid < t.myid and s.myval!=0 order by s.myid desc limit 1) where t.myid in (select myid from mytable where myval=0 order by myid for update) ;

select * from mytable order by myid;
 myid | myval
------+-------
    1 |     1
    2 | 0.123
    3 | 0.123
    4 |     5
    7 |     5