Gabungkan dua tabel acara menjadi satu garis waktu

12

Diberikan dua tabel:

CREATE TABLE foo (ts timestamp, foo text);
CREATE TABLE bar (ts timestamp, bar text);

Saya ingin menulis query yang kembali nilai-nilai untuk ts, foodan baryang mewakili pandangan terpadu yang paling nilai-nilai baru-baru ini. Dengan kata lain, jika fooberisi:

ts | foo
--------
1  | A
7  | B

dan barberisi:

ts | bar
--------
3  | C
5  | D
9  | E

Saya ingin kueri yang mengembalikan:

ts | foo | bar
--------------
1  | A   | null
3  | A   | C
5  | A   | D
7  | B   | D
9  | B   | E

Jika kedua tabel memiliki acara secara bersamaan, urutannya tidak menjadi masalah.

Saya telah berhasil membuat struktur yang dibutuhkan dengan menggunakan nilai union all dan dummy:

SELECT ts, foo, null as bar FROM foo
UNION ALL SELECT ts, null as foo, bar FROM bar

yang akan memberi saya garis waktu linier dari nilai-nilai baru, tapi saya tidak cukup bisa mengetahui bagaimana mengisi nilai nol berdasarkan baris sebelumnya. Saya sudah mencoba lagfungsi jendela, tetapi AFAICT hanya akan melihat baris sebelumnya, tidak secara rekursif mundur. Saya telah melihat CTE rekursif, tapi saya tidak yakin bagaimana mengatur kondisi awal dan terminasi.

Christopher Currie
sumber
Apakah nilai-nilai di dalam foodan barbenar - benar naik dari waktu ke waktu atau apakah test case menyesatkan dalam hal ini?
Erwin Brandstetter
2
Untuk menyelamatkan orang lain dari kerepotan, sqlfiddle.com/#!15/511414
Craig Ringer
1
Daripada mengubah sifat pertanyaan setelah jawaban diberikan, tanyakan pertanyaan baru . Anda selalu dapat menautkan ini untuk referensi. (Anda bahkan dapat memberikan jawaban Anda sendiri jika ada.) Versi aslinya harus menarik bagi masyarakat umum. Mari kita tidak berkemas banyak dalam satu pertanyaan.
Erwin Brandstetter
Maaf atas kelebihannya. Saya telah menghapus tindak lanjut dan menambahkannya sebagai pertanyaan baru .
Christopher Currie

Jawaban:

7

Gunakan a FULL [OUTER] JOIN, dikombinasikan dengan dua putaran fungsi jendela :

SELECT ts
     , min(foo) OVER (PARTITION BY foo_grp) AS foo
     , min(bar) OVER (PARTITION BY bar_grp) AS bar
FROM (
   SELECT ts, f.foo, b.bar
        , count(f.foo) OVER (ORDER BY ts) AS foo_grp
        , count(b.bar) OVER (ORDER BY ts) AS bar_grp
   FROM   foo f
   FULL   JOIN bar b USING (ts)
   ) sub;

Karena count()tidak menghitung nilai NULL, nilai ini hanya meningkat dengan setiap nilai bukan nol, sehingga membentuk grup yang akan berbagi nilai yang sama. Di bagian luar SELECT, min()(atau max()) juga mengabaikan nilai NULL, sehingga memilih satu nilai non-null per grup. Voila.

FULL JOINKasus terkait :

Ini adalah salah satu kasus di mana solusi prosedural mungkin lebih cepat, karena dapat menyelesaikan pekerjaan dalam satu pemindaian. Seperti fungsi plpgsql ini :

CREATE OR REPLACE FUNCTION f_merge_foobar()
  RETURNS TABLE(ts int, foo text, bar text) AS
$func$
#variable_conflict use_column
DECLARE
   last_foo text;
   last_bar text;
BEGIN
   FOR ts, foo, bar IN
      SELECT ts, f.foo, b.bar
      FROM   foo f
      FULL   JOIN bar b USING (ts)
      ORDER  BY 1
   LOOP
      IF foo IS NULL THEN foo := last_foo;
      ELSE                last_foo := foo;
      END IF;

      IF bar IS NULL THEN bar := last_bar;
      ELSE                last_bar := bar;
      END IF;

      RETURN NEXT;
   END LOOP;
END
$func$ LANGUAGE plpgsql;

Panggilan:

SELECT * FROM f_merge_foobar();

db <> bermain-main di sini , menunjukkan keduanya.

Jawaban terkait menjelaskan #variable_conflict use_column:

Erwin Brandstetter
sumber
Masalah yang menarik bukan. Saya pikir solusi yang efisien mungkin membutuhkan penciptaan coalescefungsi jendela -seperti.
Craig Ringer
@CraigRinger: Memang. Saya mendapati diri saya berharap, bertanya-tanya, berpikir .. bahwa ini entah bagaimana seharusnya mungkin tanpa subquery, tetapi saya gagal menemukan jalan. Itu salah satu kasus di mana fungsi plpgsql akan lebih cepat karena dapat memindai setiap tabel satu kali.
Erwin Brandstetter
@Christopher: Saya akan tertarik dengan kinerja setiap varian di pengaturan Anda. EXPLAIN ANALYZE, terbaik dari 5 ...?
Erwin Brandstetter
2
Sayang sekali Postgres belum diimplementasikan IGNORE NULLS(seperti Oracle: sqlfiddle.com/#!4/fab35/1 ).
ypercubeᵀᴹ
1
@perperangkat lunak: Ya, Oracle sederhana tidak menyimpan nilai NULL sama sekali dan akibatnya tidak dapat membedakan antara ''dan NULL.
Erwin Brandstetter