使用以前的值更新列
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 ...
结构,但我无法让它与更新语句一起工作。
解决方案的第一部分:因为您不能在 UPDATE
的 SET
子句中调用 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
想象一个样本 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 ...
结构,但我无法让它与更新语句一起工作。
解决方案的第一部分:因为您不能在 UPDATE
的 SET
子句中调用 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