Asumsi / Klarifikasi
Tidak perlu membedakan antara infinitydan membuka batas atas ( upper(range) IS NULL). (Anda bisa mendapatkannya dengan cara lain, tetapi lebih sederhana dengan cara ini.)
Karena datemerupakan tipe diskrit, semua rentang memiliki [)batas default .
Per dokumentasi:
Jenis rentang bawaan int4range,, int8rangedan daterangesemua menggunakan bentuk kanonik yang mencakup batas bawah dan mengecualikan batas atas; itu adalah [),.
Untuk tipe lain (seperti tsrange!) Saya akan menerapkan hal yang sama jika memungkinkan:
Solusi dengan SQL murni
Dengan CTE untuk kejelasan:
WITH a AS (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
)
, b AS (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM a
)
, c AS (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM b
)
SELECT daterange(min(startdate), max(enddate)) AS range
FROM c
GROUP BY grp
ORDER BY 1;
Atau , sama dengan subqueries, lebih cepat tetapi juga tidak terlalu mudah dibaca:
SELECT daterange(min(startdate), max(enddate)) AS range
FROM (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
) a
) b
) c
GROUP BY grp
ORDER BY 1;
Atau dengan satu tingkat subquery yang lebih sedikit, tetapi membalik urutan:
SELECT daterange(min(COALESCE(lower(range), '-infinity')), max(enddate)) AS range
FROM (
SELECT *, count(nextstart > enddate OR NULL) OVER (ORDER BY range DESC NULLS LAST) AS grp
FROM (
SELECT range
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
, lead(lower(range)) OVER (ORDER BY range) As nextstart
FROM test
) a
) b
GROUP BY grp
ORDER BY 1;
- Sortir jendela pada langkah kedua dengan
ORDER BY range DESC NULLS LAST(dengan NULLS 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 dengan rank IS NULL.
Menjelaskan
a: Saat memesan oleh range, hitung maksimum berjalan dari batas atas ( enddate) dengan fungsi jendela.
Ganti batas NULL (tanpa batas) dengan +/- infinityhanya untuk menyederhanakan (tidak ada kasus NULL khusus).
b: Dalam urutan sortir yang sama, jika sebelumnya enddatelebih awal dari startdatekami 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 SELECTmembangun 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 :
CREATE OR REPLACE FUNCTION f_range_agg(_tbl text, _col text)
RETURNS SETOF daterange AS
$func$
DECLARE
_lower date;
_upper date;
_enddate date;
_startdate date;
BEGIN
FOR _lower, _upper IN EXECUTE
format($$SELECT COALESCE(lower(t.%2$I),'-infinity') -- replace NULL with ...
, COALESCE(upper(t.%2$I), 'infinity') -- ... +/- infinity
FROM %1$I t
ORDER BY t.%2$I$$
, _tbl, _col)
LOOP
IF _lower > _enddate THEN -- return previous range
RETURN NEXT daterange(_startdate, _enddate);
SELECT _lower, _upper INTO _startdate, _enddate;
ELSIF _upper > _enddate THEN -- expand range
_enddate := _upper;
-- do nothing if _upper <= _enddate (range already included) ...
ELSIF _enddate IS NULL THEN -- init 1st round
SELECT _lower, _upper INTO _startdate, _enddate;
END IF;
END LOOP;
IF FOUND THEN -- return last row
RETURN NEXT daterange(_startdate, _enddate);
END IF;
END
$func$ LANGUAGE plpgsql;
Panggilan:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
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 rangeakan sangat berperan untuk kinerja dalam tabel besar:
CREATE INDEX foo on test (range);
Indeks btree terbatas penggunaannya untuk tipe rentang , tetapi kita bisa mendapatkan data pra-disortir dan bahkan mungkin hanya pemindaian indeks.