Saya mencoba untuk menggabungkan beberapa rentang tanggal (beban saya sekitar maks 500, sebagian besar kasus 10) yang mungkin tumpang tindih dengan rentang tanggal bersebelahan terbesar yang mungkin. Sebagai contoh:
Data:
CREATE TABLE test (
id SERIAL PRIMARY KEY NOT NULL,
range DATERANGE
);
INSERT INTO test (range) VALUES
(DATERANGE('2015-01-01', '2015-01-05')),
(DATERANGE('2015-01-01', '2015-01-03')),
(DATERANGE('2015-01-03', '2015-01-06')),
(DATERANGE('2015-01-07', '2015-01-09')),
(DATERANGE('2015-01-08', '2015-01-09')),
(DATERANGE('2015-01-12', NULL)),
(DATERANGE('2015-01-10', '2015-01-12')),
(DATERANGE('2015-01-10', '2015-01-12'));
Tabelnya terlihat seperti:
id | range
----+-------------------------
1 | [2015-01-01,2015-01-05)
2 | [2015-01-01,2015-01-03)
3 | [2015-01-03,2015-01-06)
4 | [2015-01-07,2015-01-09)
5 | [2015-01-08,2015-01-09)
6 | [2015-01-12,)
7 | [2015-01-10,2015-01-12)
8 | [2015-01-10,2015-01-12)
(8 rows)
Hasil yang diinginkan:
combined
--------------------------
[2015-01-01, 2015-01-06)
[2015-01-07, 2015-01-09)
[2015-01-10, )
Representasi visual:
1 | =====
2 | ===
3 | ===
4 | ==
5 | =
6 | =============>
7 | ==
8 | ==
--+---------------------------
| ====== == ===============>
postgresql
aggregate
range-types
Villiers Strauss
sumber
sumber
Jawaban:
Asumsi / Klarifikasi
Tidak perlu membedakan antara
infinity
dan membuka batas atas (upper(range) IS NULL
). (Anda bisa mendapatkannya dengan cara lain, tetapi lebih sederhana dengan cara ini.)infinity
dalam tipe rentang PostgreSQLKarena
date
merupakan tipe diskrit, semua rentang memiliki[)
batas default . Per dokumentasi:Untuk tipe lain (seperti
tsrange
!) Saya akan menerapkan hal yang sama jika memungkinkan:Solusi dengan SQL murni
Dengan CTE untuk kejelasan:
Atau , sama dengan subqueries, lebih cepat tetapi juga tidak terlalu mudah dibaca:
Atau dengan satu tingkat subquery yang lebih sedikit, tetapi membalik urutan:
ORDER BY range DESC NULLS LAST
(denganNULLS LAST
) untuk mendapatkan urutan sortir yang terbalik sempurna . Ini harus lebih murah (lebih mudah diproduksi, cocok dengan urutan indeks yang disarankan dengan sempurna) dan akurat untuk kasus sudut denganrank IS NULL
.Menjelaskan
a
: Saat memesan olehrange
, hitung maksimum berjalan dari batas atas (enddate
) dengan fungsi jendela.Ganti batas NULL (tanpa batas) dengan +/-
infinity
hanya untuk menyederhanakan (tidak ada kasus NULL khusus).b
: Dalam urutan sortir yang sama, jika sebelumnyaenddate
lebih awal daristartdate
kami memiliki celah dan memulai rentang baru (step
).Ingat, batas atas selalu dikecualikan.
c
: Bentuk grup (grp
) dengan menghitung langkah-langkah dengan fungsi jendela lain.Di luar
SELECT
membangun berkisar dari batas bawah ke batas atas di setiap kelompok. Voa.Jawaban terkait erat pada SO dengan penjelasan lebih lanjut:
Solusi prosedural dengan plpgsql
Berfungsi untuk nama tabel / kolom apa saja, tetapi hanya untuk tipe
daterange
.Solusi prosedural dengan loop biasanya lebih lambat, tetapi dalam kasus khusus ini saya berharap fungsi menjadi jauh lebih cepat karena hanya membutuhkan pemindaian sekuensial tunggal :
Panggilan:
Logikanya mirip dengan solusi SQL, tetapi kita bisa puas dengan satu pass.
SQL Fiddle.
Terkait:
Bor biasa untuk menangani input pengguna dalam SQL dinamis:
Indeks
Untuk masing-masing solusi ini, indeks btree (default) polos
range
akan sangat berperan untuk kinerja dalam tabel besar:Indeks btree terbatas penggunaannya untuk tipe rentang , tetapi kita bisa mendapatkan data pra-disortir dan bahkan mungkin hanya pemindaian indeks.
sumber
EXPLAIN ( ANALYZE, TIMING OFF)
dan membandingkan terbaik dari lima.max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
untuk apa? Tidak bisakah itu adilCOALESCE(upper(range), 'infinity') as enddate
? AFAIKmax() + over (order by range)
akan kembali keupper(range)
sini.Saya datang dengan ini:
Masih perlu sedikit diasah, tetapi idenya adalah sebagai berikut:
+
) gagal, kembalikan kisaran yang sudah dibangun dan inisialisasi ulangsumber
generate_series()
setiap baris, terutama jika ada rentang terbuka ...Beberapa tahun yang lalu saya menguji berbagai solusi (antara lain beberapa serupa dengan yang dari @ErwinBrandstetter) untuk menggabungkan periode yang tumpang tindih pada sistem Teradata dan saya menemukan yang berikut ini yang paling efisien (menggunakan Fungsi Analitik, versi Teradata yang lebih baru memiliki fungsi bawaan untuk tugas itu).
maxEnddate
maxEnddate
menggunakan baris berikutnyaLEAD
dan Anda hampir selesai. Hanya untuk baris terakhirLEAD
mengembalikan aNULL
, untuk menyelesaikan ini menghitung tanggal akhir maksimum semua baris partisi di langkah 2 danCOALESCE
itu.Kenapa lebih cepat? Tergantung pada langkah data aktual # 2 mungkin sangat mengurangi jumlah baris, sehingga langkah selanjutnya perlu beroperasi pada subset kecil saja, selain itu menghilangkan agregasi.
biola
Karena ini tercepat di Teradata, saya tidak tahu apakah itu sama untuk PostgreSQL, akan menyenangkan untuk mendapatkan beberapa angka kinerja aktual.
sumber
Untuk bersenang-senang, saya mencobanya. Saya menemukan ini menjadi metode tercepat dan terbersih untuk melakukan ini. Pertama kita mendefinisikan fungsi yang menggabungkan jika ada tumpang tindih atau jika dua input berdekatan, jika tidak ada tumpang tindih atau kedekatan kita hanya mengembalikan daterange pertama. Petunjuk
+
adalah rentang serikat dalam konteks rentang.Lalu kami menggunakannya seperti ini,
sumber
('2015-01-01', '2015-01-03'), ('2015-01-03', '2015-01-05'), ('2015-01-05', '2015-01-06')
.