การใช้ฟังก์ชั่นหน้าต่างเพื่อส่งต่อค่าที่ไม่ใช่ค่าว่างในพาร์ติชัน


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 เป็นโมฆะ) ประเมินเป็น 1 หรือ 0 สำหรับวัตถุประสงค์ในการเรียงลำดับเพื่อให้ฉันได้รับค่าที่ไม่ใช่ค่าแรกในพาร์ติชัน

ข้างต้นไม่ได้ให้ผลลัพธ์ตามที่ฉันต้องการ


คุณสามารถวางpg_dumpข้อมูลทดสอบของคุณแทนที่จะวางข้อมูลในเอาต์พุต psql และสคีมาสำหรับตารางได้หรือไม่ pg_dump -t table -d databaseเราต้องการสร้างและCOPYคำสั่ง
Evan Carroll


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ᵀᴹ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.