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 date
ayrık türüdür tüm aralıkları varsayılan sahip [)
sınırları.
Belgelere göre:
Dahili aralığı türleri int4range
, int8range
ve daterange
tü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 enddate
daha 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
: grp
Başka bir pencere işleviyle adımları sayarak gruplar ( ) oluşturun.
Dış SELECT
yapı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 range
iç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.