A solution could be something like
CREATE TABLE test_ranges (
big_range int4range,
small_range int4range,
test_case text
);
INSERT INTO test_ranges
VALUES
('[1,9]'::int4range, '[5,7]'::int4range, 'smaller range splits bigger into two'),
('[1,9]'::int4range, '[1,9]'::int4range, 'smaller is the same as bigger'),
('[1,9]'::int4range, '[0,9]'::int4range, 'smaller is bigger'),
('[1,9]'::int4range, '[1,4]'::int4range, 'smaller removes the left part of bigger (same effect expected on the right side)'),
('[1,9]'::int4range, 'empty'::int4range, 'smaller is empty'),
('empty'::int4range, '[5,7]'::int4range, 'bigger is empty');
/* please note that you have to reorganize the query a bit
in order to get the 'halves' as a set */
SELECT
CASE WHEN isempty(small_range)
THEN big_range
ELSE big_range - int4range(lower(small_range), upper(big_range))
END AS first_half,
big_range - int4range(lower(big_range), upper(small_range)) AS second_half,
test_case
FROM test_ranges;
Note that
test=# SELECT '[1,5)'::int4range = '[1,4]'::int4range;
?column?
──────────
t
There is a small SQLFiddle to show this at work. Thanks to ypercube to bring my attention to one corner case that the original query did not cover.