我的任务: 变量
x
和 def
分别是计数器和布尔值。两者都从零开始。
x > 2
,则从该点开始def = 1
。def = 1
那么为了使其再次变为零,x
必须连续 3 个周期等于 0,否则 def
仍为 1
。我有 SAS 背景,现在开始更广泛地使用 Postgresql:
data have;
input n x;
datalines;
1 0
2 1
3 2
4 3
5 4
6 2
7 1
8 0
9 1
10 0
11 0
12 0
13 0
14 1
15 2
16 3
17 4
;
run;
data want(drop=prev_: cur_m def rename=def_new=def);
set have;
retain prev_def prev_cur_m;
if x > 2 then def = 1;
else if prev_def = 1 and x > 0 then def = 1;
else def = 0;
if _n_ = 1 then cur_m = -1;
else if def = 1 then cur_m = -1;
else if prev_def = 1 and def = 0 then cur_m = 0;
else if -1 < prev_cur_m < 3 and x > 0 then cur_m = 0;
else if -1 < prev_cur_m < 3 and x = 0 then cur_m + 1;
else cur_m = -1;
if _n_ = 1 then def_new = 0;
else if def = 1 then def_new = 1;
else if -1 < cur_m < 3 then def_new = 1;
else def_new = 0;
output;
prev_def = def;
prev_cur_m = cur_m;
run;
据我了解,这可以通过游标来实现,并尝试使用此post解决问题。然而,当我需要使用前一行的值来计算当前的值时,我陷入了困境。这是代码:
create table have (
n int,
x int
) ;
insert into have (n,x)
values (1,0),(2,1),(3,2),(4,3),(5,4),(6,2),
(7,1),(8,0),(9,1),(10,0),(11,0),(12,0),
(13,0),(14,1),(15,2),(16,3),(17,4);
create or replace function calc_dpd()
returns table (
n int,
x int,
def int,
cure_m int,
def_new int )
language plpgsql as $f$
declare rec record;
begin
for rec in (select * from have order by n) loop
if rec.n = 1 then
def = 0;
cure_m = 0;
def_new = 0;
end if;
if x > 2 then
def = 1;
end if;
-- here I need to test if def from previous row = 1 and current def = 0;
select rec.n, rec.x
into n,x;
return next;
end loop;
end $f$;
PL/pgSQL 可以做到这一点,但您不需要其复杂性和开销来实现您所需要的:这一切都可以通过简单的声明式 SQL 中的窗口函数来实现:demo
0=all(array[])
检查数组中的所有元素是否为 0
lag(x,that_many_rows_back,0)
从那么多行中获取值 x
。或者 0
如果这一行之前没有那么多行。over()
子句中就地定义窗口,也可以在 window
子句中单独定义窗口。如果您需要重复使用,后者是更好的选择。bool_or()
检查此值之前的任何值是否符合条件。这里它仅限于窗口框架,因此它只涉及自上次 def
在 x
中获得 3 个零后关闭以来的行。boolean::int
cast 将 false
映射到 0
,将 true
映射到 1
。with cte as (
select *
,(x > 2) as exceeded_2
,(0 = all(array[ x
,lag(x,1,0)over w1
,lag(x,2,0)over w1
]
)
) as should_switch
from have
window w1 as (order by n) )
,cte2 as (
select *,sum(should_switch::int)over(order by n) as def_on_period
from cte
)
select n,x,(bool_or(exceeded_2) over w2)::int as def
from cte2
window w2 as (partition by def_on_period
order by n);
n | x | 定义 |
---|---|---|
1 | 0 | 0 |
2 | 1 | 0 |
3 | 2 | 0 |
4 | 3 | 1 第一个x>2 |
5 | 4 | 1 |
6 | 2 | 1 |
7 | 1 | 1 |
8 | 0 | 1 |
9 | 1 | 1 |
10 | 0 | 1 |
11 | 0 | 1 |
12 | 0 | 0 x 中的第三个零 |
13 | 0 | 0 |
14 | 1 | 0 |
15 | 2 | 0 |
16 | 3 | 1 x>2 |
17 | 4 | 1 |