Menggunakan fungsi jendela untuk meneruskan nilai non-null pertama di partisi

12

Pertimbangkan tabel yang mencatat kunjungan

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

Pertimbangkan contoh data ini (stempel waktu disederhanakan sebagai penghitung)

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

Saya mencoba meneruskan nilai non-null terakhir dari orang tersebut ke semua kunjungannya di masa mendatang hingga nilai tersebut berubah (yaitu menjadi nilai non-nol berikutnya).

Kumpulan hasil yang diharapkan terlihat seperti ini:

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

Usaha saya terlihat seperti ini:

 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

Catatan: the (somevalue is null) mengevaluasi ke 1 atau 0 untuk keperluan penyortiran sehingga saya bisa mendapatkan nilai non-null pertama di partisi.

Di atas tidak memberi saya hasil yang saya kejar.

maxTrialfire
sumber
Bisakah Anda hanya menempelkan pg_dumpuntuk data pengujian Anda daripada menempelkan data dalam output psql, dan skema untuk tabel? pg_dump -t table -d databasekita membutuhkan perintah create dan COPY.
Evan Carroll
1
@a_horse_with_no_name yang pantas menjadi jawaban.
ypercubeᵀᴹ

Jawaban:

12

Kueri berikut mencapai hasil yang diinginkan:

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

Catat pernyataan kasus nol - jika IGNORE_NULL didukung oleh fungsi jendela postgres, ini tidak diperlukan (seperti yang disebutkan oleh @ ypercubeᵀᴹ)

maxTrialfire
sumber
5
Juga sederhanacount(somevalue) over (...)
ypercubeᵀᴹ
5

Masalahnya adalah dalam kategori masalah kesenjangan dan pulau. Sangat disayangkan bahwa Postgres belum diimplementasikan IGNORE NULLdalam fungsi-fungsi jendela seperti FIRST_VALUE(), kalau tidak itu akan sepele, dengan perubahan sederhana dalam kueri Anda.

Mungkin ada banyak cara untuk ini diselesaikan menggunakan fungsi jendela atau CTE rekursif.

Tidak yakin apakah itu cara yang paling efisien tetapi CTE rekursif dapat menyelesaikan masalah:

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ᵀᴹ
sumber
Memang memang memecahkan masalah namun lebih kompleks dari yang seharusnya. Lihat jawaban saya di bawah ini
maxTrialfire
1
Ya, jawaban Anda sepertinya bagus!
ypercubeᵀᴹ