根据条件对两行和其间的所有行进行分组

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 中,我添加了行数 grpvalue = '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

【讨论】:

以上是关于根据条件对两行和其间的所有行进行分组的主要内容,如果未能解决你的问题,请参考以下文章

根据行和列对矩阵中的所有元素进行排名

SQL Server:根据多个条件从组中选择特定行

根据 3 到 4 个条件对数据库表中的行进行计数和分组查询

Oracle SQL:将当前行和前两行的excel嵌套if条件转换为SQL

根据数字对数组的行进行分组

当必须根据条件对记录进行分组时如何选择最多 x 行