ข้อสมมติฐาน / คำชี้แจง
ไม่จำเป็นต้องแยกความแตกต่างระหว่าง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
สร้างด้านนอกช่วงจากล่างถึงบนบนในแต่ละกลุ่ม voila
คำตอบที่เกี่ยวข้องกับ SO อย่างใกล้ชิดพร้อมคำอธิบายเพิ่มเติม:
วิธีการแก้ปัญหาขั้นตอนด้วย 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 แต่เราสามารถทำได้ด้วยการผ่านครั้งเดียว
ซอ Fiddle
ที่เกี่ยวข้อง:
การเจาะตามปกติสำหรับการจัดการอินพุตผู้ใช้ใน SQL แบบไดนามิก:
ดัชนี
สำหรับโซลูชันแต่ละรายการดัชนี btree ธรรมดา (ค่าเริ่มต้น) range
จะเป็นเครื่องมือสำหรับประสิทธิภาพในตารางขนาดใหญ่:
CREATE INDEX foo on test (range);
ดัชนี btree มีการใช้งานที่ จำกัด สำหรับประเภทช่วงแต่เราสามารถรับข้อมูลที่จัดเรียงไว้ล่วงหน้าและอาจสแกนดัชนีเท่านั้น