Это не тривиально.Сочетание кросс-таблицы с перекрывающимися и пересекающимися диапазонами, а также угловые случаи (объединение одинаковых дат начала / окончания) сверху.Очень сложно решить с помощью операций, основанных на множествах, т.е. чистого SQL.
Вместо этого я предлагаю процедурное решение в PL / pgSQL.Должно также работать хорошо, поскольку требуется только одно (растровое индексирование) сканирование таблицы:
CREATE OR REPLACE FUNCTION f_student_xtab(VARIADIC _student_ids int[])
RETURNS TABLE (
student_id int
, "primary" text
, secondary text
, start_date date
, end_date date) AS
$func$
DECLARE
r record;
BEGIN
student_id := -1; -- init with impossible value
FOR r IN
SELECT t.student_id, t.site, t.primary_or_secondary = 'Primary' AS prim, l.range_end, l.date
FROM tbl t
CROSS JOIN LATERAL (
VALUES (false, t.start_date)
, (true , t.end_date)
) AS l(range_end, date)
WHERE t.student_id = ANY (_student_ids)
ORDER BY t.student_id, l.date, range_end -- start of range first
LOOP
IF r.student_id <> student_id THEN
student_id := r.student_id;
IF r.prim THEN "primary" := r.site;
ELSE secondary := r.site;
END IF;
start_date := r.date;
ELSIF r.range_end THEN
IF r.date < start_date THEN
-- range already reported
IF r.prim THEN "primary" := NULL;
ELSE secondary := NULL;
END IF;
start_date := NULL;
ELSE
end_date := r.date;
RETURN NEXT;
IF r.prim THEN
"primary" := NULL;
IF secondary IS NULL THEN start_date := NULL;
ELSE start_date := r.date + 1;
END IF;
ELSE
secondary := NULL;
IF "primary" IS NULL THEN start_date := NULL;
ELSE start_date := r.date + 1;
END IF;
END IF;
end_date := NULL;
END IF;
ELSE -- range starts
IF r.date > start_date THEN
-- range already running
end_date := r.date - 1;
RETURN NEXT;
END IF;
start_date := r.date;
end_date := NULL;
IF r.prim THEN "primary" := r.site;
ELSE secondary := r.site;
END IF;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Вызов:
SELECT * FROM f_student_xtab(1,2,3);
Или:
SELECT * FROM f_student_xtab(VARIADIC '{1,2,3}');
db <> fiddle здесь - с расширенным тестовым набором
About VARIADIC
: