Varsayımlar / Açıklamalar
infinityÜst sınırı ( upper(range) IS NULL) ayırıp açmaya gerek yoktur . (Her iki şekilde de alabilirsiniz, ancak bu şekilde daha basit.)
Yana dateayrık türüdür tüm aralıkları varsayılan sahip [)sınırları.
Belgelere göre:
Dahili aralığı türleri int4range, int8rangeve daterangetüm kullanım düşük bağlanmış ve hariç üst sınırı içeren standart bir formu; yani [).
Diğer türler için (örneğin tsrange!) Mümkünse aynısını uygularım:
Saf SQL ile çözüm
Netlik için CTE'lerle:
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;
Ya da alt sorgular için de aynı şey daha hızlı ama daha az kolay okunabilir:
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;
Veya daha az alt sorgu düzeyiyle, ancak sıralama düzenini ters çevirerek:
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;
- Mükemmel tersine çevrilmiş sıralama düzeni elde etmek için pencereyi ikinci adımda
ORDER BY range DESC NULLS LAST(ile NULLS LAST) sıralayın. Bu, daha ucuz (üretilmesi daha kolay, önerilen dizinin sıralama düzenini mükemmel şekilde eşleştirir) ve köşe durumları için doğru olmalıdır .
rank IS NULL
Açıklamak
a: Sipariş verirken range, bir pencere işleviyle üst sınırın ( ) çalışma maksimum değerini hesaplayın enddate. Basitleştirmek için
NULL sınırlarını (sınırsız) +/- ile değiştirin infinity(özel NULL durumlar yok).
b: Aynı sıralama düzeninde, bir öncekinden enddatedaha erkense startdate, bir boşluğa sahip oluruz ve yeni bir aralık başlatırız ( step).
Unutmayın, üst sınır her zaman hariç tutulur.
c: grpBaşka bir pencere işleviyle adımları sayarak gruplar ( ) oluşturun.
Dış SELECTyapıda her grupta alttan üst sınıra kadar değişir. Voila.
SO hakkında daha fazla açıklama ile yakından ilgili cevap:
Plpgsql ile prosedür çözümü
Herhangi bir tablo / sütun adı için çalışır, ancak yalnızca tür için çalışır daterange.
Döngülerle prosedürel çözümler genellikle daha yavaştır, ancak bu özel durumda, yalnızca tek bir sıralı taramaya ihtiyaç duyduğundan işlevin önemli ölçüde daha hızlı olmasını beklerim :
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;
Aramak:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
Mantık SQL çözümlerine benzer, ancak tek bir geçişle yapabiliriz.
SQL Fiddle.
İlgili:
Dinamik SQL'de kullanıcı girişini işlemek için olağan matkap:
indeks
Bu çözümlerin her biri rangeiçin büyük tablolardaki performans için düz (varsayılan) bir btree dizini etkili olacaktır:
CREATE INDEX foo on test (range);
Bir btree dizini aralık türleri için sınırlı kullanımlıdır , ancak önceden sıralanmış veriler ve hatta yalnızca dizin içeren bir tarama elde edebiliriz.