Giả định / Làm rõ
Không cần phân biệt giữa infinity
và mở giới hạn trên ( upper(range) IS NULL
). (Bạn có thể có một trong hai cách, nhưng cách này đơn giản hơn.)
Vì date
là một loại riêng biệt, tất cả các phạm vi có [)
giới hạn mặc định .
Mỗi tài liệu:
Việc xây dựng trong các loại dải int4range
, int8range
và daterange
tất cả sử dụng một hình thức kinh điển bao gồm các ràng buộc thấp hơn và không bao gồm phía trên ràng buộc; có nghĩa là, [)
.
Đối với các loại khác (như tsrange
!) Tôi sẽ thực thi tương tự nếu có thể:
Giải pháp với SQL thuần túy
Với CTE cho rõ ràng:
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;
Hoặc , tương tự với các truy vấn con, nhanh hơn nhưng ít dễ đọc hơn:
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;
Hoặc với một cấp độ truy vấn ít hơn, nhưng lật thứ tự sắp xếp:
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;
- Sắp xếp cửa sổ trong bước thứ hai với
ORDER BY range DESC NULLS LAST
(với NULLS LAST
) để có được thứ tự sắp xếp đảo ngược hoàn hảo . Điều này sẽ rẻ hơn (dễ sản xuất hơn, phù hợp với thứ tự sắp xếp của chỉ số được đề xuất một cách hoàn hảo) và chính xác cho các trường hợp góc với rank IS NULL
.
Giải thích
a
: Trong khi sắp xếp theo thứ tự range
, hãy tính mức tối đa đang chạy của giới hạn trên ( enddate
) với hàm cửa sổ.
Thay thế giới hạn NULL (không giới hạn) bằng +/- infinity
chỉ để đơn giản hóa (không có trường hợp NULL đặc biệt nào).
b
: Trong cùng một thứ tự sắp xếp, nếu trước enddate
đó sớm hơn startdate
chúng ta có một khoảng cách và bắt đầu một phạm vi mới ( step
).
Hãy nhớ rằng, giới hạn trên luôn luôn bị loại trừ.
c
: Tạo nhóm ( grp
) bằng cách đếm các bước với chức năng cửa sổ khác.
Trong SELECT
phạm vi xây dựng bên ngoài từ dưới đến giới hạn trên trong mỗi nhóm. Voilá.
Câu trả lời liên quan chặt chẽ về SO với giải thích thêm:
Giải pháp thủ tục với plpgsql
Hoạt động cho bất kỳ tên bảng / cột, nhưng chỉ cho loại daterange
.
Các giải pháp thủ tục với các vòng lặp thường chậm hơn, nhưng trong trường hợp đặc biệt này, tôi hy vọng chức năng sẽ nhanh hơn đáng kể vì nó chỉ cần một lần quét tuần tự duy nhất :
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;
Gọi điện:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
Logic tương tự như các giải pháp SQL, nhưng chúng ta có thể thực hiện với một lần duy nhất.
Câu đố SQL.
Liên quan:
Mũi khoan thông thường để xử lý đầu vào của người dùng trong SQL động:
Mục lục
Đối với mỗi giải pháp này, chỉ số btree (mặc định) đơn giản range
sẽ là công cụ để thực hiện trong các bảng lớn:
CREATE INDEX foo on test (range);
Một chỉ mục btree được sử dụng hạn chế cho các loại phạm vi , nhưng chúng tôi có thể nhận được dữ liệu được sắp xếp trước và thậm chí có thể quét chỉ mục.