在MySQL中分组重叠范围的数据

问题描述

有没有一种简单的方法可以避免使用游标来转换它:

+-------+------+-------+
| Group | From | Until |
+-------+------+-------+
| X     | 1    | 3     |
+-------+------+-------+
| X     | 2    | 4     |
+-------+------+-------+
| Y     | 5    | 7     |
+-------+------+-------+
| X     | 8    | 10    |
+-------+------+-------+
| Y     | 11   | 12    |
+-------+------+-------+
| Y     | 12   | 13    |
+-------+------+-------+

对此:

+-------+------+-------+
| Group | From | Until |
+-------+------+-------+
| X     | 1    | 4     |
+-------+------+-------+
| Y     | 5    | 7     |
+-------+------+-------+
| X     | 8    | 10    |
+-------+------+-------+
| Y     | 11   | 13    |
+-------+------+-------+

到目前为止,我已经尝试为每行分配一个ID,并为该ID分配GROUP BY,但是如果不使用游标,我将无法获得更近的距离。

解决方法

SELECT `Group`,`From`,`Until`
FROM ( SELECT `Group`,ROW_NUMBER() OVER (PARTITION BY `Group` ORDER BY `From`) rn
       FROM test t1
       WHERE NOT EXISTS ( SELECT NULL
                          FROM test t2
                          WHERE t1.`From` > t2.`From`
                            AND t1.`From` <= t2.`Until`
                            AND t1.`Group` = t2.`Group` ) ) t3
JOIN ( SELECT `Group`,`Until`,ROW_NUMBER() OVER (PARTITION BY `Group` ORDER BY `From`) rn
       FROM test t1
       WHERE NOT EXISTS ( SELECT NULL
                          FROM test t2
                          WHERE t1.`Until` >= t2.`From`
                            AND t1.`Until` < t2.`Until`
                            AND t1.`Group` = t2.`Group` ) ) t4 USING (`Group`,rn)

fiddle

必须在任何重叠类型(部分重叠,相邻,完全包含)下工作。

如果From和/或Until为NULL,将不起作用。



您可以添加英文说明吗? – ysth

第一个子查询搜索加入的范围开始(请参见小提琴-它是单独执行的)-它在不位于任何其他范围的中间/结尾的组中搜索From值(允许起点相等)

第二个子查询对连接的范围Until执行相同的操作。

两者都枚举发现值递增。

外部查询只是将每个范围的起点和终点连接成一行。

,

如果使用的是MYSQL 8+版本,则可以使用row_number获得所需的结果:

Demo

  SELECT MIN(`FROM`) START,MAX(`UNTIL`) END,`GROUP` FROM (
SELECT A.*,ROW_NUMBER() OVER(ORDER BY `FROM`) RN_FROM,ROW_NUMBER() OVER(PARTITION BY `GROUP` ORDER BY `UNTIL`) RN_UNTIL
FROM Table_lag A) X  
GROUP BY `GROUP`,(RN_FROM - RN_UNTIL) 
ORDER BY START;
,

我将为此使用递归CTE:

JavaScript Events

cable-ready:before-morph

cable-ready:after-morph

锚表达式(DateTimeFormatter isoFormat = ISODateTimeFormat.dateTimeParser(); Date date = isoFormat.parseDateTime("2020-09-09T09:58:00+0000").toDate(); // Wed Sep 09 05:58:00 EDT 2020 )查找所有&组合,而不会与之组合,因此(最终输出中每一行都有一行):

然后递归查询为我们的每一行添加合并间隔的行。

然后按组和从(这些列的名称很糟糕)进行分组,以获取最大的
每个起始组/起始间隔。

https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=9efa508504b80e44b73c952572394b76

或者,您可以使用一组简单的联接和子查询来完成此操作,而无需CTE或窗口函数:

with recursive intervals (`Group`,`Until`) as (
    select distinct t1.Group,t1.From,t1.Until
    from Table_lag t1
    where not exists (
        select 1
        from Table_lag t2
        where t1.Group=t2.Group
        and t1.From between t2.From and t2.Until+1
        and (t1.From,t1.Until) <> (t2.From,t2.Until)
    )
    union all
    select t1.Group,t2.Until
    from intervals t1
    join Table_lag t2
        on t2.Group=t1.Group
        and t2.From between t1.From and t1.Until+1
        and t2.Until > t1.Until
)
select `Group`,max(`Until`) as Until
from intervals
group by `Group`,`From`
order by `From`,`Group`;

(我将此处的列重命名为不需要反引号。)

这里有一个选择来获取我们将报告的可报告间隔的所有开始,再加入另一个类似的选择(您可以使用CTE以避免冗余)来查找同一组的可报告间隔的以下开始(如果有的话)。将其包装在子查询中,以获取以下可报告间隔的组,起始值和起始值。然后,它只需要联接在该范围内开始的所有其他记录并选择最大结束值。

https://dbfiddle.uk/?rdbms=mysql_5.5&fiddle=151cc933489c299f7beefa99e1959549

,

您只能使用某些“孤岛”技术使用窗口函数来做到这一点。

想法是使用lag()和窗口sum()构建具有相同组和重叠范围的连续记录组。然后,您可以汇总组:

select grp,min(c_from) c_from,max(c_until) c_until
from (
    select
        t.*,sum(lag_c_until < c_from) over(partition by grp order by c_from) mygrp
    from (
        select
            t.*,lag(c_until,1,c_until) over(partition by grp order by c_from) lag_c_until
        from mytable t
    ) t
) t
group by grp,mygrp

您选择的列名称与SQL关键字(groupfrom)冲突,因此我将它们重命名为grpc_fromc_until。 / p>

Demo on DB Fiddle -首先归功于ysth,以创建小提琴:

grp | c_from | c_until
:-- | -----: | ------:
X   |      1 |       4
Y   |      5 |       7
X   |      8 |      10
Y   |     11 |      13