Postgresql 选择将具有范围的行拆分为具有较小范围的多行的选项?
我有两个表,其中包含分类的tsrange值。每个表中的范围在每个类别中不重叠,但b中的范围可能与a中的范围重叠 我想做的是将这两个表组合成一个CTE,用于另一个查询。组合值需要是表a中的tsranges减去表b中具有相同类别的任何重叠tsranges 复杂的是,如果重叠的b.周期包含在a.周期内,则减法的结果是两行。Postgres Range-运算符不支持此操作,因此我创建了一个返回1或2行的函数:Postgresql 选择将具有范围的行拆分为具有较小范围的多行的选项?,postgresql,Postgresql,我有两个表,其中包含分类的tsrange值。每个表中的范围在每个类别中不重叠,但b中的范围可能与a中的范围重叠 我想做的是将这两个表组合成一个CTE,用于另一个查询。组合值需要是表a中的tsranges减去表b中具有相同类别的任何重叠tsranges 复杂的是,如果重叠的b.周期包含在a.周期内,则减法的结果是两行。Postgres Range-运算符不支持此操作,因此我创建了一个返回1或2行的函数: create function subtract_tsrange( a tsrange , b
create function subtract_tsrange( a tsrange , b tsrange )
returns table (period tsrange)
language 'plpgsql' as $$
begin
if a @> b and not isempty(b) and lower(a) <> lower(b) and upper(b) <> upper(a)
then
period := tsrange(lower(a), lower(b), '[)');
return next;
period := tsrange(upper(b), upper(a), '[)');
return next;
else
period := a - b;
return next;
end if;
return;
end
$$;
也可能有几个b.period与a.period重叠,因此a中的一行可能会被拆分为许多周期较短的行
现在,我想创建一个select,它接受a中的每一行并返回:
如果没有相同类别的重叠b.期间,则为原始a.期间
或
1行或多行表示原始a.句点减去具有相同类别的所有重叠b.句点。
在阅读了很多其他帖子后,我想我应该以某种方式将selectlateral与我的函数结合起来使用,但我仍然不知道该如何使用??我们在谈论博士后9.6 btw 注意:您的问题很容易推广到每种范围类型,因此我将在回答中使用anyrange伪类型,但您不必这样做。事实上,正因为如此,我必须为范围类型创建一个通用构造函数,因为PostgreSQL尚未定义它:
create or replace function to_range(t anyrange, l anyelement, u anyelement, s text default '[)', out to_range anyrange)
language plpgsql as $func$
begin
execute format('select %I($1, $2, $3)', pg_typeof(t)) into to_range using l, u, s;
end
$func$;
当然,您可以使用适当的范围构造函数,而不是使用to_范围调用
此外,我将使用numrange类型进行测试,因为它比tsrange类型更容易创建和检查,但我的答案也应该适用于此
答复:
我重写了您的函数来处理任何类型的边界,包括、排除甚至无界范围。此外,当一个伟大的答案是广义答案时,它将返回一个空结果集!当我开始理解这个问题时的第一个问题是:range_div中最不合适的“{}”是什么?我不明白这意味着什么,我假设这不是一个打字错误,因为你似乎真的运行了这个code@JesperWe“{}”实际上是一个空数组,它是一个未知类型的文本,但是它的类型将被强制为适当范围的数组类型-但是这很难用它来表示,所以我使用了未知文本。是一个集合返回函数,它只是一个接一个地生成所有元素的结果,但是在结果集中,而不是在数组中。我喜欢这个解决方案,我觉得它非常简洁。它也有用!
create or replace function to_range(t anyrange, l anyelement, u anyelement, s text default '[)', out to_range anyrange)
language plpgsql as $func$
begin
execute format('select %I($1, $2, $3)', pg_typeof(t)) into to_range using l, u, s;
end
$func$;
create or replace function range_div(a anyrange, b anyrange)
returns setof anyrange
language sql as $func$
select * from unnest(case
when b is null or a <@ b then '{}'
when a @> b then array[
to_range(a, case when lower_inf(a) then null else lower(a) end,
case when lower_inf(b) then null else lower(b) end,
case when lower_inc(a) then '[' else '(' end ||
case when lower_inc(b) then ')' else ']' end),
to_range(a, case when upper_inf(b) then null else upper(b) end,
case when upper_inf(a) then null else upper(a) end,
case when upper_inc(b) then '(' else '[' end ||
case when upper_inc(a) then ']' else ')' end)
]
else array[a - b]
end)
$func$;
with recursive r as (
select *
from a
union
select r.id, r.category, d
from r
left join b using (category)
cross join range_div(r.period, b.period) d -- this is in fact an implicit lateral join
where r.period && b.period
)
select r.*
from r
left join b on r.category = b.category and r.period && b.period
where not isempty(r.period) and b.period is null
create table a (id serial primary key, category int, period numrange);
create table b (id serial primary key, category int, period numrange);
insert into a (category, period) values (1, '[1,4]'), (1, '[2,5]'), (1, '[3,6]'), (2, '(1,6)');
insert into b (category, period) values (1, '[2,3)'), (1, '[1,2]'), (2, '[3,3]');
id | category | period
3 | 1 | [3,6]
1 | 1 | [3,4]
2 | 1 | [3,5]
4 | 2 | (1,3)
4 | 2 | (3,6)