基于前面行的值:当 x>2 时重复 1,当连续 3 行 x=0 时重复 0

问题描述 投票:0回答:1

我的任务: 变量

x
def
分别是计数器和布尔值。两者都从零开始。

  1. 如果在任何时间点
    x > 2
    ,则从该点开始
    def = 1
  2. 现在,如果
    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$;
sql postgresql sas window-functions gaps-and-islands
1个回答
0
投票

PL/pgSQL 可以做到这一点,但您不需要其复杂性和开销来实现您所需要的:这一切都可以通过简单的声明式 SQL 中的窗口函数来实现:demo

  1. 0=all(array[])
    检查数组中的所有元素是否为
    0
  2. lag(x,that_many_rows_back,0)
    从那么多行中获取值
    x
    。或者
    0
    如果这一行之前没有那么多行。
  3. 您可以在
    over()
    子句中就地定义窗口,也可以在
    window
    子句中单独定义窗口。如果您需要重复使用,后者是更好的选择。
  4. bool_or()
    检查此值之前的任何值是否符合条件。这里它仅限于窗口框架,因此它只涉及自上次
    def
    x
    中获得 3 个零后关闭以来的行。
  5. 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
© www.soinside.com 2019 - 2024. All rights reserved.