Usando a função window para transmitir o primeiro valor não nulo em uma partição

12

Considere uma tabela que registra visitas

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

Considere este exemplo de dados (registro de data e hora simplificado como contador)

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

Estou tentando levar adiante o último valor não nulo da pessoa para todas as suas futuras visitas até que esse valor mude (ou seja, se torne o próximo valor não nulo).

O conjunto de resultados esperado se parece com o seguinte:

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

Minha tentativa é assim:

 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

Nota: o (algum valor é nulo) é avaliado como 1 ou 0 para fins de classificação, para que eu possa obter o primeiro valor não nulo na partição.

O exposto acima não me dá o resultado que estou procurando.

maxTrialfire
fonte
Você poderia colar os pg_dumpdados dos seus testes em vez de colar os dados em uma saída psql e o esquema da tabela? pg_dump -t table -d databaseprecisamos dos COPYcomandos create e
Evan Carroll
11
@a_horse_with_no_name que merece ser uma resposta.
ypercubeᵀᴹ

Respostas:

12

A consulta a seguir alcança o resultado desejado:

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);

Observe a declaração de caso nulo - se IGNORE_NULL fosse suportado pelas funções da janela do postgres, isso não seria necessário (como mencionado por @ ypercubeᵀᴹ)

maxTrialfire
fonte
5
Além disso, o simplescount(somevalue) over (...)
ypercubeᵀᴹ
5

O problema está na categoria de problemas e ilhas. É uma pena que o Postgres ainda não tenha implementado IGNORE NULLem funções de janela como FIRST_VALUE(), caso contrário, seria trivial, com uma simples alteração na sua consulta.

Provavelmente existem muitas maneiras de resolver isso usando funções de janela ou CTEs recursivas.

Não tenho certeza se é a maneira mais eficiente, mas uma CTE recursiva resolve o problema:

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 ;
ypercubeᵀᴹ
fonte
De fato, resolve o problema, no entanto, é mais complexo do que precisa ser. Veja minha resposta abaixo
maxTrialfire
11
Sim, sua resposta parece boa!
ypercubeᵀᴹ