Извините, это был действительно сложный вопрос. Но я наконец нашел способ сделать это, используя некоторые инструменты, которые я создал ранее. Хитрость заключалась в том, чтобы перебирать вложенную таблицу чисел, чтобы получить каждый элемент.
Итак, первая часть была серийным генератором, который я беззастенчиво позаимствовал у Постгреса. Есть и другие способы генерации чисел, но это довольно эффективно.
CREATE OR REPLACE FUNCTION generate_series(
p_start NUMBER,
p_end NUMBER
) RETURN NUMBER_TABLE PIPELINED IS
BEGIN
FOR i IN p_start .. p_end LOOP
PIPE ROW(i);
END LOOP;
RETURN;
END;
И вторая часть - это возможность подписывать элемент коллекции в SQL.
CREATE OR REPLACE FUNCTION get_item(p1 PERIOD_TABLE, idx NUMBER)
RETURN PERIOD IS
BEGIN
RETURN p1(idx);
END;
И, наконец, все вместе:
FUNCTION range_intersect(
p1 PERIOD_TABLE,
p2 PERIOD_TABLE
) RETURN PERIOD_TABLE IS
v_return PERIOD_TABLE;
v_len1 NUMBER(8);
v_len2 NUMBER(8);
BEGIN
v_len1 := p1.last;
v_len2 := p2.last;
WITH pa1 AS (
SELECT get_item(p1, column_value) AS period
FROM TABLE(generate_series(1, v_len1))
),
pa2 AS (
SELECT get_item(p2, column_value) AS period
FROM TABLE(generate_series(1, v_len2))
)
SELECT period(start_time, MIN(end_time))
BULK COLLECT INTO v_return
FROM (
SELECT (pa1.period).first() AS start_time
FROM pa1
WHERE contains(p1, (pa1.period).prev()) = 0
AND contains(p2, (pa1.period).first()) = 1
UNION ALL
SELECT (pa2.period).first() AS start_time
FROM pa2
WHERE contains(p1, (pa2.period).prev()) = 0
AND contains(p2, (pa2.period).first()) = 1
) s_start
... snip...
Не просто, но выполнимо.