按分组名称获取行索引区间,保持顺序和间隙
我有一张包含大量数据的表,可以把其中的数据简化成一组非唯一的可选名称和索引:
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 |
到目前为止,我已经能够使用 LEAD 和 LAG 函数来检测区间的起点和终点,但还没能让它们真正实现分组。我的想法是将 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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。