it-swarm.com.de

Verwenden der Fensterfunktion zum Übertragen des ersten Nicht-Null-Werts in einer Partition

Stellen Sie sich eine Tabelle vor, in der Besuche aufgezeichnet werden

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

Betrachten Sie diese Beispieldaten (Zeitstempel als Zähler vereinfacht)

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

Ich versuche, den letzten Nicht-Null-Wert der Person auf alle zukünftigen Besuche zu übertragen, bis sich dieser Wert ändert (dh der nächste Nicht-Null-Wert wird).

Die erwartete Ergebnismenge sieht folgendermaßen aus:

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

Mein Versuch sieht so aus:

 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

Hinweis: Der Wert (somevalue is null) wird zum Sortieren mit 1 oder 0 ausgewertet, damit ich den ersten Wert ungleich Null in der Partition erhalten kann.

Das Obige gibt mir nicht das Ergebnis, nach dem ich suche.

11
maxTrialfire

Die folgende Abfrage erzielt das gewünschte Ergebnis:

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

Beachten Sie die Null-Fall-Anweisung - wenn IGNORE_NULL von Postgres-Fensterfunktionen unterstützt würde, wäre dies nicht erforderlich (wie von @ ypercubeᵀᴹ erwähnt).

11
maxTrialfire

Das Problem liegt in der Kategorie der Lücken und Inseln. Es ist schade, dass Postgres IGNORE NULL Noch nicht in Fensterfunktionen wie FIRST_VALUE() implementiert hat, da dies sonst trivial wäre und eine einfache Änderung in Ihrer Abfrage erforderlich wäre.

Es gibt wahrscheinlich viele Möglichkeiten, dies mithilfe von Fensterfunktionen oder rekursiven CTEs zu lösen.

Ich bin mir nicht sicher, ob dies der effizienteste Weg ist, aber ein rekursiver CTE löst das Problem:

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 ;
4
ypercubeᵀᴹ