Використання функції вікна для перенесення першого ненульового значення в розділ


12

Розгляньте таблицю, яка записує відвідування

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

Я намагаюся перенести останню ненулеву деяку цінність людини на всі його майбутні візити, поки це значення не зміниться (тобто стане наступним ненульовим) значенням.

Очікуваний набір результатів виглядає приблизно так:

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 is null) оцінюється 1 або 0, щоб я міг отримати перше ненулеве значення в розділі.

Сказане не дає мені результату, за яким я прагну.


Не могли б ви просто вставити pg_dumpдля своїх тестових даних, а не вставляти їх у висновок psql та схему для таблиці? pg_dump -t table -d databaseнам потрібні створення та COPYкоманди.
Еван Керролл


1
@a_horse_with_no_name, яке заслуговує на відповідь.
ypercubeᵀᴹ

Відповіді:


12

Наступний запит забезпечує бажаний результат:

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ᵀᴹ)


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

5

Проблема полягає у категорії проблем, що стосуються прогалин та островів. Шкода, що 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 ;

Це дійсно вирішує проблему, проте її складніша, ніж повинна бути. Дивіться мою відповідь нижче
maxTrialfire

1
Так, ваша відповідь здається гарною!
ypercubeᵀᴹ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.