Припущення / уточнення
Не потрібно розрізняти infinity
та відкривати верхню межу ( upper(range) IS NULL
). (Ви можете мати його в будь-якому випадку, але це простіше.)
Оскільки date
це дискретний тип, всі діапазони мають [)
межі за замовчуванням .
За документацію:
Вбудовані типи дальності int4range
, int8range
і daterange
будь-яке використання канонічної формі , яка включає в себе нижню межу і НЕ включає верхню межу; тобто [)
.
Для інших типів (як tsrange
!) Я б застосував те саме, якщо можливо:
Рішення з чистим SQL
З CTE для наочності:
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;
Або те саме, що підзапроси, швидше, але менш легко читати:
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;
Або з одним менш рівним запитом, але гортаючи сортування:
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;
- Сортуйте вікно на другому кроці за допомогою
ORDER BY range DESC NULLS LAST
(з NULLS LAST
), щоб отримати ідеально перевернутий порядок сортування. Це повинно бути дешевшим (простіше у виробництві, відмінно відповідає порядку упорядкованого індексу) та точним для кутових справ rank IS NULL
.
Поясніть
a
: Під час замовлення range
обчислюйте максимум виконання верхньої межі ( enddate
) за допомогою віконної функції.
Замініть NULL межі (без обмежень) на +/-infinity
просто для спрощення (жодних спеціальних випадків NULL).
b
: У такому ж порядку сортування, якщо попередній enddate
раніше, ніж у startdate
нас, буде розрив і запустити новий діапазон ( step
).
Пам'ятайте, верхня межа завжди виключається.
c
: Групи форм (grp
), рахуючи кроки з іншою функцією вікна.
У зовнішній SELECT
збірці діапазон від нижньої до верхньої межі кожної групи. Войла.
Тісно пов’язана відповідь на ТА з додатковими поясненнями:
Процедурний розчин з plpgsql
Працює для будь-якої таблиці таблиці / стовпців, але лише для типу daterange
.
Процедурні рішення з циклами, як правило, повільніше, але в цьому спеціальному випадку я очікую, що функція буде значно швидшою, оскільки для цього потрібне лише одне послідовне сканування :
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;
Виклик:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
Логіка схожа на рішення SQL, але ми можемо зробити це за один прохід.
SQL Fiddle.
Пов'язані:
Звичайна дриль для обробки вводу користувача в динамічному SQL:
Покажчик
Для кожного з цих рішень звичайний (за замовчуванням) індекс btree range
буде важливим для роботи у великих таблицях:
CREATE INDEX foo on test (range);
Індекс btree має обмежене використання для типів діапазону , але ми можемо отримати попередньо відсортовані дані та, можливо, навіть сканування лише з індексом.