Utilizzo della funzione finestra per trasferire il primo valore non nullo in una partizione


11

Considera una tabella che registra le visite

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

Considera questi dati di esempio (timestamp semplificato come contatore)

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

Sto cercando di riportare l'ultimo valore non nullo della persona a tutte le sue visite future fino a quando quel valore cambia (cioè diventa il successivo valore non nullo).

Il set di risultati previsto è simile al seguente:

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

Il mio tentativo è simile al seguente:

 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: il (somevalue è null) restituisce 1 o 0 ai fini dell'ordinamento in modo da poter ottenere il primo valore non nullo nella partizione.

Quanto sopra non mi dà il risultato che sto cercando.


Potresti semplicemente incollare i pg_dumpdati dei test anziché incollarli in un output psql e lo schema per la tabella? pg_dump -t table -d databaseabbiamo bisogno di creare e COPYcomandi.
Evan Carroll,


1
@a_horse_with_no_name che merita di essere una risposta.
ypercubeᵀᴹ

Risposte:


11

La seguente query ottiene il risultato desiderato:

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

Nota l'istruzione case null - se IGNORE_NULL fosse supportato dalle funzioni della finestra di postgres questo non sarebbe necessario (come menzionato da @ ypercubeᵀᴹ)


5
Anche il semplicecount(somevalue) over (...)
ypercubeᵀᴹ

4

Il problema è nella categoria di problemi tra le isole e le lacune. È un peccato che Postgres non abbia ancora implementato IGNORE NULLfunzioni della finestra come FIRST_VALUE(), altrimenti sarebbe banale, con una semplice modifica nella tua query.

Probabilmente ci sono molti modi per risolvere questo problema usando le funzioni della finestra o CTE ricorsivi.

Non sono sicuro che sia il modo più efficiente ma un CTE ricorsivo risolve il 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 ;

Risolve davvero il problema, tuttavia è più complesso di quanto debba essere. Vedi la mia risposta di seguito
maxTrialfire il

1
Sì, la tua risposta sembra buona!
ypercubeᵀᴹ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.