창 함수를 사용하여 파티션에서 첫 번째 널이 아닌 값을 전달


12

방문을 기록한 테이블을 고려하십시오.

create table visits (
  person varchar(10),
  ts timestamp, 
  somevalue varchar(10) 
)

이 예제 데이터를 고려하십시오 (시간 소인은 카운터로 단순화 됨)

ts| person    |  somevalue
-------------------------
1 |  bob      |null
2 |  bob      |null
3 |  jim      |null
4 |  bob      |  A
5 |  bob      | null
6 |  bob      |  B
7 |  jim      |  X
8 |  jim      |  Y
9 |  jim      |  null

값이 변경 될 때까지 (예 : 널이 아닌 다음 값이 될 때까지) 모든 향후 방문에 해당 사용자의 마지막 널이 아닌 일부 값을 전달하려고합니다.

예상되는 결과 집합은 다음과 같습니다.

ts|  person   | somevalue | carry-forward 
-----------------------------------------------
1 |  bob      |null       |   null
2 |  bob      |null       |   null
3 |  jim      |null       |   null
4 |  bob      |  A        |    A
5 |  bob      | null      |    A
6 |  bob      |  B        |    B
7 |  jim      |  X        |    X
8 |  jim      |  Y        |    Y
9 |  jim      |  null     |    Y

내 시도는 다음과 같습니다

 select *, 
  first_value(somevalue) over (partition by person order by (somevalue is null), ts rows between UNBOUNDED PRECEDING AND current row  ) as carry_forward

 from visits  
 order by ts

참고 : 정렬의 목적으로 (일부 값은 null)은 1 또는 0으로 평가되므로 파티션에서 null이 아닌 첫 번째 값을 얻을 수 있습니다.

위의 결과는 내가 얻은 결과를 제공하지 않습니다.


pg_dumppsql 출력에 데이터를 붙여 넣지 않고 테스트 데이터를 붙여 넣을 수 있습니까? pg_dump -t table -d databasecreate와 COPYcommand 가 필요 합니다.
Evan Carroll


1
대답이 될만한 @a_horse_with_no_name.
ypercubeᵀᴹ

답변:


12

다음 쿼리는 원하는 결과를 얻습니다.

select *, first_value(somevalue) over w as carryforward_somevalue
from (
  select *, sum(case when somevalue is null then 0 else 1 end) over (partition by person order by id ) as value_partition
  from test1

) as q
window w as (partition by person, value_partition order by id);

널 (null) 구문을 참고하십시오-postgres 창 함수가 IGNORE_NULL을 지원하면 필요하지 않습니다 (@ ypercubeᵀᴹ에서 언급 한 바와 같이).


5
또한 간단한count(somevalue) over (...)
ypercubeᵀᴹ

5

문제는 갭앤 아일랜드 범주의 문제입니다. Postgres가와 IGNORE NULL같은 창 함수에서 아직 구현하지 않은 것은 유감입니다 FIRST_VALUE(). 그렇지 않으면 쿼리가 간단하게 변경되어 사소한 것입니다.

창 함수 또는 재귀 CTE를 사용하여이를 해결하는 방법에는 여러 가지가있을 수 있습니다.

그것이 가장 효율적인 방법인지 확실하지 않지만 재귀 적 CTE가 문제를 해결합니다.

with recursive 
    cf as
    (
      ( select distinct on (person) 
            v.*, v.somevalue as carry_forward
        from visits as v
        order by person, ts
      ) 
      union all
        select 
            v.*, coalesce(v.somevalue, cf.carry_forward)
        from cf
          join lateral  
            ( select v.*
              from visits as v
              where v.person = cf.person
                and v.ts > cf.ts
              order by ts
              limit 1
            ) as v
            on true
    )
select cf.*
from cf 
order by ts ;

실제로 문제를 해결하지만 필요한 것보다 더 복잡합니다. 아래 내 답변보기
maxTrialfire

1
예, 당신의 대답은 좋아 보입니다!
ypercubeᵀᴹ
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.