Utilisation de la fonction de fenêtre pour reporter la première valeur non nulle dans une partition


11

Considérons un tableau qui enregistre les visites

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

Considérez cet exemple de données (horodatage simplifié comme compteur)

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

J'essaie de reporter la dernière valeur non nulle de la personne à toutes ses visites futures jusqu'à ce que cette valeur change (c'est-à-dire devienne la prochaine valeur non nulle).

L'ensemble de résultats attendu ressemble à ceci:

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

Ma tentative ressemble à ceci:

 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

Remarque: la (une valeur est nulle) est évaluée à 1 ou 0 pour le tri afin que je puisse obtenir la première valeur non nulle dans la partition.

Ce qui précède ne me donne pas le résultat que je recherche.


Pourriez-vous simplement coller le pg_dumppour vos données de test plutôt que de coller les données dans une sortie psql et le schéma de la table? pg_dump -t table -d databasenous avons besoin de la création et des COPYcommandes.
Evan Carroll


1
@a_horse_with_no_name qui mérite d'être une réponse.
ypercubeᵀᴹ

Réponses:


11

La requête suivante atteint le résultat souhaité:

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

Notez la déclaration null case - si IGNORE_NULL était pris en charge par les fonctions de fenêtre postgres, cela ne serait pas nécessaire (comme mentionné par @ ypercubeᵀᴹ)


5
Aussi le simplecount(somevalue) over (...)
ypercubeᵀᴹ

4

Le problème se situe dans la catégorie des lacunes et des îles. C'est dommage que Postgres n'ait pas encore implémenté IGNORE NULLde fonctions de fenêtres comme FIRST_VALUE(), sinon ce serait trivial, avec un simple changement dans votre requête.

Il existe probablement de nombreuses façons de résoudre ce problème en utilisant des fonctions de fenêtre ou des CTE récursifs.

Je ne sais pas si c'est le moyen le plus efficace mais un CTE récursif résout le problème:

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 ;

Il résout effectivement le problème, mais il est plus complexe qu'il ne devrait l'être. Voir ma réponse ci
maxTrialfire

1
Oui, votre réponse semble bonne!
ypercubeᵀᴹ
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.