ウィンドウ関数を使用して、パーティション内の最初の非null値を繰り越します


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

私は、その値が変更される(つまり、次の非null値になる)まで、その人の最後の非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 is null)は、ソートの目的で1または0に評価されるため、パーティション内の最初のnull以外の値を取得できます。

上記は私が求めている結果を私に与えません。


あなただけ貼り付けることができ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);

null caseステートメントに注意してください-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 2016年

1
はい、あなたの答えは良いようです!
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.