If you have a fixed number of objects that you want to cross-reference, you can use the cross-connection for each of them and construct the intersection (using the * operator on ranges).
Using a cross join like this is probably less efficient. The following example is more about explaining a more complex example below.
WITH td AS ( SELECT 1 AS entity_id, '2014-01-01'::timestamp AS begin_time, '2014-01-31'::timestamp AS end_time UNION SELECT 1, '2014-02-01', '2014-02-28' UNION SELECT 1, '2014-04-01', '2014-04-30' UNION SELECT 2, '2014-01-15', '2014-02-20' UNION SELECT 2, '2014-04-15', '2014-05-05' UNION SELECT 4, '2014-01-20', '2014-04-20' ) ,ranges AS (
In this case, multiple cross-connects are probably less efficient, because in reality you do not need to have all the possible combinations of each range in reality, since isempty(r1.the_range * r2.the_range) enough to make isempty(r1.the_range * r2.the_range * r3.the_range) true.
I do not think that you can avoid access to each person on time, since you want all of them to meet in any case.
Which can help create a phased set of intersections by cross-connecting the accessibility of each person with the previous subset that you calculated using another recursive CTE ( intersections in the example below). Then you create the intersections step by step and get rid of empty ranges, like stored arrays:
WITH RECURSIVE td AS ( SELECT 1 AS entity_id, '2014-01-01'::timestamp AS begin_time, '2014-01-31'::timestamp AS end_time UNION SELECT 1, '2014-02-01', '2014-02-28' UNION SELECT 1, '2014-04-01', '2014-04-30' UNION SELECT 2, '2014-01-15', '2014-02-20' UNION SELECT 2, '2014-04-15', '2014-05-05' UNION SELECT 4, '2014-01-20', '2014-04-20' ) ,ranges AS (
I'm not sure if this is likely to work better, unfortunately. You will probably need a larger dataset to have meaningful tests.