Это работает методом грубой силы (я переименовал ваши столбцы range_start и range_end, чтобы избежать конфликта с зарезервированным словом "конец"):
select *
from t cross join t t2
where t2.source <> t.source
and box(point(t2.range_start,t2.range_start),point(t2.range_end,t2.range_end))
&& box(point(t.range_start,t.range_start),point(t.range_end,t.range_end))
или
select *
from t
where exists (select 1 from t t2
where t2.source <> t.source and box(point(t2.range_start,t2.range_start),point(t2.range_end,t2.range_end))
&& box(point(t.range_start,t.range_start),point(t.range_end,t.range_end)))
После этого вы сможете использовать индекс GiST, который может сделать это более эффективным (seq scan + index scan):
create index t_range_idx on t using gist (box(point(range_start,range_start),point(range_end,range_end))
Эта функция может помочь в понимании путем очистки SQL:
create function range(not_before int, not_after int) returns box
strict immutable language sql
as $$ select box(point($1,$1),point($2,$2)) $$;
При этом вы можете написать:
select * from t where range(range_start,range_end) && range(10,20);
и заметить, что оператор box && box
означает «перекрытия».