按分组名称获取行索引区间,保持顺序和间隙

后端开发 2026-07-11

我有一张包含大量数据的表,可以把其中的数据简化成一组非唯一的可选名称和索引:

SELECT *
FROM unnest(ARRAY[
    NULL, NULL,
    'foo', 'foo', 'foo',
    NULL, NULL, NULL,
    'bar', 'bar',
    NULL,
    'foo', 'foo'
]) WITH ORDINALITY _(name, index);

我想把名称分组,生成一个带有区间的索引,同时保留中间的空缺。最终的目标大概是这样的:

名称 区间
NULL 1-2
foo 3-5
NULL 6-8
bar 9-10
NULL 11
foo 12-13

到目前为止,我已经能够使用 LEADLAG 函数来检测区间的起点和终点,但还没能让它们真正实现分组。我的想法是将 index 列与 ARRAY_AGG 聚合,然后用首尾元素来创建一个区间。我的当前查询是:

WITH dat AS (
  SELECT *
  FROM unnest(ARRAY[
        NULL, NULL,
        'foo', 'foo', 'foo',
        NULL, NULL, NULL,
        'bar', 'bar',
        NULL,
        'foo',
        'foo'
    ]) WITH ORDINALITY _(name, index)
)
SELECT *,
    CASE
         WHEN (
            LEAD(index, 1) OVER (ORDER BY name, index) = index + 1
            AND LAG(index, 1) OVER (ORDER BY name, index) = index - 1
         ) THEN 'middle'
         WHEN LEAD(index, 1) OVER (ORDER BY name, index) = index + 1 THEN 'start'
         WHEN LAG(index, 1) OVER (ORDER BY name, index) = index - 1 THEN 'end'
         ELSE 'solo'
    END pos
FROM dat
ORDER BY INDEX;

我另一种想法是做一个简单的聚合(SELECT name, array_agg(index) FROM data GROUP BY name;),再用一个函数生成结果,但如果可能的话,我更愿意用一个普通查询来完成。

解决方案

内置 range_agg() 可以自行处理缺口与岛屿的问题,内部:

select name,unnest(range_agg(int8range(index,index+1)))
from dat
group by 1
order by 2
名称 这些是 [ 下界包含、) 上界排除
null [1,3) 上界排除,因此是 [1,2]
foo [3,6)
null [6,9)
bar [9,11)
null [11,12)
foo [12,14)

每一行都会变成一个单元素的区间,这些区间会合并成每个 name 的一个大多重区间,然后 unnest() 将连续的部分输出。将其保留为一个真正的区间类型,就能解锁原生的区间函数和运算符,例如包含性检查 <@

如果你确实想要自定义的文本表示形式,可以在 concat_ws() 中提取区间边界:

select name, concat_ws('-',lower(r),nullif(upper(r)-1,lower(r))) as range
from(select name, range_agg(int8range(index,index+1))as mr
     from dat
     group by 1)_
cross join unnest(mr) as r
order by r;
名称 区间
null 1-2
foo 3-5
null 6-8
bar 9-10
null 11
foo 12-13

在没有索引的情况下对3 万行的执行时间: demo1 at db<>fiddle

变体 平均值 最小值 最大值 总和 标准差 众数
ranges 0.513552 0.406988 0.639515 3.594861 0.077809 0.406988
gaps_islands_valnik 0.598761 0.484624 0.872527 4.191326 0.131729 0.484624
lukasz_szozda_1cte 0.717282 0.484675 1.146719 5.020975 0.223619 0.484675

它不会使用索引,因此在这些条件存在时,基于索引的方法的扩展性更好: demo2 at db<>fiddle

变体 平均值 最小值 最大值 总和 标准差 众数
lukasz_szozda_1cte 0.094761 0.05992 0.271856 1.895213 0.051649 0.05992
ranges 0.166492 0.104947 0.574869 3.329831 0.124854 0.104947
gaps_islands_valnik 0.17523 0.104779 0.75683 3.504595 0.146324 0.104779
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章