根据条件对两行和其间的所有行进行分组
Posted
技术标签:
【中文标题】根据条件对两行和其间的所有行进行分组【英文标题】:Group two rows and all rows in between on condition 【发布时间】:2014-08-19 08:42:30 【问题描述】:通过以下查询定义的表给出如下屏幕截图所示的输出:
select
id,
value,
case when value = 'foo'
and random() <= 0.5
then 't' else null
end as to_group
from
(select
generate_series(1,100) as id,
case when random() <= 0.2 then 'foo' else 'bar' end as value
)t1
如何将标记为'foo'
和't'
的所有行与前面的'foo'
行分组(不管它是否有to_group = 't'
)和所有封闭的'bar'
行?
在给定的示例中,这些行是 33 - 37。
【问题讨论】:
提供你的样本输出 顺便说一句:这是昨天一个问题的转贴。 (可能被删除了,因为我从这里看不到) 嗨 joop:我删除并尝试以更清晰的方式重写它;) 好吧:你没能说得更清楚:不清楚你所说的分组是什么意思。 .正如@dude 所说,您应该将预期的输出添加到问题中。 (顺便说一句:它看起来像一个间隙和岛屿类型的问题,可能可以通过递归查询来解决) 【参考方案1】:特别的困难是我们需要 两个 组中的一些带有('foo', TRUE)
的行 - 在一个的末尾和下一个的开头。所以我们必须添加另一个实例。
为了使其更易于理解,并使用一组稳定的行,我将您的示例值放入一个临时表中:
CREATE TEMP TABLE t AS
SELECT id, value
,CASE WHEN value = 'foo' AND random() < 0.5
THEN TRUE ELSE null
END AS to_group
FROM (
SELECT id, CASE WHEN random() < 0.2 THEN 'foo' ELSE 'bar' END AS value
FROM generate_series(1,100) id
) sub;
对标志使用boolean
数据类型使其更简单。
然后我的查询被简化为:
WITH cte AS (
SELECT *, count(value = 'foo' OR NULL) OVER (ORDER BY id) AS grp
FROM t
)
SELECT grp, min(id) AS min_id, max(id) AS max_id
FROM (
SELECT id, value, to_group, grp FROM cte
UNION ALL
SELECT id, value, to_group, grp - 1 FROM cte WHERE to_group
) sub
GROUP BY grp
HAVING count(value = 'foo' OR NULL) = 2
ORDER BY grp;
解释
在 CTE cte
中,我添加了行数 grp
和 value = 'foo'
。中间的其他行得到相同的数字:
value = 'foo' OR NULL
对于所有不是'foo'
的值都为NULL。
count()
只计算非空值。
您的群组成员现在拥有相同的grp
号码,加上下一行是to_group
。
此技术详解:Compute percents from SUM() in the same SELECT sql query
如前所述,特殊的困难是我们需要一些行两次。所以我们在子查询sub
中添加另一个带有UNION ALL
的实例。在此过程中,我将 grp
的副本数减 1,这样您的组现在就完成了。
现在最终的 SELECT 可以是 GROUP BY grp
。
有效组有 两 行 value = 'foo'
。
【讨论】:
@joop:我添加了一个链接来详细解释那个。【参考方案2】:以下解决方案正是我想要的:
create temp table t as
select
id,
value,
case when value = 'foo'
and random() <= 0.5
then 't' else null
end as to_group
from
(select
generate_series(1,100) as id,
case when random() <= 0.2 then 'foo' else 'bar' end as value
)t1;
SELECT array_agg(id) from
(SELECT *, sum(group_flag) over (ORDER BY id) AS group_nr FROM
(select *,
case WHEN (to_group = 't' and value = 'foo')
or (next_to_group = 't' and value = 'bar') THEN NULL
ELSE '1'::integer END AS group_flag
from(
select distinct on (id) id, value, to_group, foo_id, next_to_group
from (
select * from t
left join
(select id as foo_id, to_group as next_to_group from t where value = 'foo')n
on
n.foo_id > t.id
order by id, foo_id
)t1
)t2
)t3
)t4 group by group_nr
【讨论】:
以上是关于根据条件对两行和其间的所有行进行分组的主要内容,如果未能解决你的问题,请参考以下文章