Использование функции окна для переноса первого ненулевого значения в разделе

11

Рассмотрим таблицу, в которой записываются посещения

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

Рассмотрим данные этого примера (временная метка упрощена как счетчик)

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

Я пытаюсь перенести последнее ненулевое somevalue человека на все его будущие посещения, пока это значение не изменится (то есть не станет следующим ненулевым) значением.

Ожидаемый набор результатов выглядит следующим образом:

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

Моя попытка выглядит так:

 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

Примечание: (somevalue равно null) оценивается как 1 или 0 для целей сортировки, поэтому я могу получить первое ненулевое значение в разделе.

Выше не дает мне результат, который я после.

maxTrialfire
источник
Не могли бы вы просто вставить pg_dumpданные теста, а не вставить данные в вывод psql и схему для таблицы? pg_dump -t table -d databaseнам нужны команды create и COPY.
Эван Кэрролл
1
@a_horse_with_no_name, который заслуживает того, чтобы быть ответом.
ypercubeᵀᴹ

Ответы:

11

Следующий запрос достигает желаемого результата:

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

Обратите внимание на нулевой регистр - если IGNORE_NULL поддерживается оконными функциями postgres, в этом нет необходимости (как упоминалось в @ ypercubeᵀᴹ)

maxTrialfire
источник
5
Также простоcount(somevalue) over (...)
ypercubeᵀᴹ
4

Проблема в категории проблем с пробелами и островами. Жаль, что Postgres еще не реализовал IGNORE NULLв окнах такие функции, какFIRST_VALUE() , иначе, это было бы тривиально, с простым изменением в вашем запросе.

Вероятно, есть много способов решить эту проблему с помощью оконных функций или рекурсивных CTE.

Не уверен, что это самый эффективный способ, но рекурсивный CTE действительно решает проблему:

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ᵀᴹ
источник
Это действительно решает проблему, однако она более сложна, чем должна быть. Смотрите мой ответ ниже
maxTrialfire
1
Да, ваш ответ кажется хорошим!
ypercubeᵀᴹ