我的任务: 变量
x and def
分别是计数器和布尔值。两者都从零开始。如果在任何时间点x > 2 then def = 1
。现在 if def = 1
为了再次变为零,x
必须连续 3 个周期等于 0,否则它仍然等于 1。
我有 sas 背景,现在开始更广泛地使用 Postgresql。有人可以帮我在sql中执行以下操作吗?
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
$$
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
$$
PL/pgSQL 功能强大,您不需要所有的功能、复杂性和开销来实现您所需要的:这一切都可以通过简单的声明性 SQL 中的窗口函数实现:演示
0=all(array[])
检查数组中的所有元素是否为 0
lag(x,that_many_rows_back,0)
从那么多行中获取值 x
。或者 0
如果这一行之前没有那么多行。over()
子句中就地定义窗口,也可以在 window
子句中单独定义窗口。如果您需要重复使用,后者是更好的选择。bool_or()
检查此值之前的任何值是否符合条件。这里它仅限于窗口框架,因此它只涉及自上次 def
在 x
中获得 3 个零后关闭以来的行。with cte as (
select *
,(x > 2) 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) 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>3 |
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>3 |
17 | 4 | 1 |