问题描述
我想根据Postgresql中相邻行之间另一个列值的差异来更新表中的特定列。
这是一个测试设置:
CREATE TABLE test(
main INTEGER,sub_id INTEGER,value_t INTEGER);
INSERT INTO test (main,sub_id,value_t)
VALUES
(1,1,8),(1,2,7),3,3),4,85),5,40),(2,1),41);
我的目标是从main
开始的每个组sub_id 1
中,通过按以下顺序检查升序来确定diff
中的哪个值超过某个阈值(例如 -10) sub_id
。在达到阈值之前,我要标记每个传递的行 AND ,方法是在列FALSE
中填充一个值,例如newval
。 1
。
我应该使用循环还是有更智能的解决方案?
伪代码中的任务描述:
FOR i in GROUP [PARTITION BY main ORDER BY sub_id]:
DO until diff > 10 OR diff <-10
SET newval = 1 AND LEAD(newval) = 1
解决方法
您的问题很难理解,“ value_t”列与该问题无关,您忘记了在SQL中定义“ diff”列。
无论如何,这是您的解决方案:
WITH data AS (
SELECT main,sub_id,value_t,abs(value_t
- lead(value_t) OVER (PARTITION BY main ORDER BY sub_id)) > 10 is_evil
FROM test
)
SELECT main,CASE max(is_evil::int)
OVER (PARTITION BY main ORDER BY sub_id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
WHEN 1 THEN NULL ELSE 1 END newval
FROM data;
我正在使用CTE准备数据(计算一行是否为“ evil”),然后使用“ max”窗口函数检查当前行之前是否存在任何“ evil”行。分区。
,基本SELECT
尽快:
SELECT *,bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
FROM (
SELECT *,value_t - lag(value_t,1,value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
FROM test
) sub;
细点
-
您的思想模型围绕窗口函数
lead()
演化。但是它的对应对象lag()
的使用效率更高,因为在大间隙之前的行中没有逐个错误。 或者 ,将lead()
使用倒序排列(ORDER BY sub_id DESC
)。 -
为避免在分区的第一行使用
NULL
,请默认提供value_t
作为第3个参数,这将使diff0
而不是NULL。lead()
和lag()
都具有该功能。 -
diff BETWEEN -10 AND 10
比@diff < 11
快一点(也更清晰,更灵活)。 (@
being the "absolute value" operator,等同于abs()
function。)
外窗函数中的 -
bool_or()
orbool_and()
对于将所有行标记到最大间隙可能是最便宜的。
您的UPDATE
在达到阈值之前,我想标记每个通过的行和条件为
FALSE
的一行,方法是在列newval
中填充一个值,例如1
。
再次,尽快。
UPDATE test AS t
SET newval = 1
FROM (
SELECT main,bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
FROM (
SELECT main,value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
FROM test
) sub
) u
WHERE (t.main,t.sub_id) = (u.main,u.sub_id)
AND u.flag;
细点
-
计算单个查询中的所有值通常比关联子查询要快得多。
-
添加的WHERE条件
AND u.flag
确保我们仅更新实际需要更新的行。
如果某些行在newval
中可能已经具有正确的值,请添加另一个子句以避免这些空更新:AND t.newval IS DISTINCT FROM 1
参见: -
SET newval = 1
分配一个常量(即使在这种情况下我们可以使用实际计算出的值),也便宜一些。
db 提琴here
,在汇总子查询中存在:
UPDATE test u
SET value_t = NULL
WHERE EXISTS (
SELECT * FROM (
SELECT main,ABS(value_t - lag(value_t)
OVER (PARTITION BY main ORDER BY sub_id) ) AS absdiff
FROM test
) x
WHERE x.main = u.main
AND x.sub_id <= u.sub_id
AND x.absdiff >= 10
)
;
SELECT * FROM test
ORDER BY main,sub_id;
结果:
UPDATE 3
main | sub_id | value_t
------+--------+---------
1 | 1 | 8
1 | 2 | 7
1 | 3 | 3
1 | 4 |
1 | 5 |
2 | 1 | 3
2 | 2 | 1
2 | 3 | 1
2 | 4 | 8
2 | 5 |
(10 rows)