MySQL:如何将范围分区组合成最大可能的连续范围

标签 mysql

我一直在尝试完成一个相当复杂的 SQL 查询(也许很简单?)来压缩包含重复信息的表。我在 SequelPro 中使用 MySQL 5.7.14。我是一名新手 SQL 用户,对联接、联合等有基本的了解。我认为这个需要一个带有一些分组依据的子查询,但我不知道如何最好地做到这一点。 下表说明了我正在尝试做的一个简单示例:

table

对于每个 col_1 重复条目,当 col_2 和 3 设置的范围(分别是范围的开始和结束)重叠时,我想压缩为单个条目。对于 col_4 和 5,应报告落在该范围内的条目中的最大值。在上面的示例中,在 col_1 中,a 有三个重叠范围,我想将其压缩为 col_1 的最小值和 col_2 的最大值,以及 col_4 和 5 的最大值。对于 col_2 中的“b”,有两个范围(31-50, 12-15) 不重叠,因此它将按原样返回两行。对于 c,它将返回范围为 100-300 的一行,并且 col_4 和 col_5 的值分别为 3、2。此示例所需的完整结果如下所示:

query output

我应该补充一点,某些地方有“空”值,应将其视为零。 有人知道最好、最简单的方法吗? 预先感谢您!!

更新:我尝试使用建议的范围设置查询,但收到错误。查询如下:

WITH a AS (SELECT range 
  , lower(col_2) AS startdate
  , max(upper(col_3)) OVER (ORDER BY range) AS `end`
   FROM   `combine`
   )
, b AS (
   SELECT *, lag(`end`) OVER (ORDER BY range) < `start` OR NULL AS step
   FROM   a
   )
, c AS (
   SELECT *, count(step) OVER (ORDER BY range) AS grp
   FROM   b
   )
SELECT daterange(min(`start`), max(`end`)) AS range
FROM   c
GROUP  BY grp
ORDER  BY 1;

我收到的错误是: 您的 SQL 语法有错误;检查与您的 MySQL 服务器版本相对应的手册,了解在 'a AS 附近使用的正确语法(SELECT range , lower(col_2) AS 开始日期 , max(upper(col_3)) OVE' 在第 1 行

最佳答案

这并不是一件小事,但它可以在一个查询中完成。

困难的部分是将一组间隔组合成最大可能的连续间隔。解决方案详见this post .

要获得您想要的结果,您现在需要:

  1. 使用链接中给出的查询计算 col1 中每个值的最大可能连续间隔。

根据您的示例值,结果将是:

col_1 lower_bound upper_bound
a     20          60
b     12          15
b     31          50
c     100         300
  • 将其中一个大间隔与 your_table 中的每一行相关联。 。每行只能有一个这样的间隔,所以我们 INNER JOIN :

    SELECT my_table.*, large_intervals.lower_bound, large_intervals.upper_bound FROM my_table INNER JOIN (my_awesome_query(your_table)) large_intervals ON large_intervals.col1 = my_table.col1 AND large_intervals.lower_bound <= my_table.col2 AND large_intervals.upper_bound >= my_table.col3

  • 你会得到:

    col1 col2 col3 col4 col5 lower_bound upper_bound
    a    45   50   1    0    20          60
    a    50   61   6    0    20          60
    a    20   45   0    5    20          60
    b    31   50   0    1    31          50
    b    12   15   5    0    12          15
    c    100  200  3    2    100         300
    c    150  300  1    2    100         300
    
  • 那就很简单了,只需按 col1、lower_bound、upper 进行分组即可:
  • SELECT col1, lower_bound AS col2, upper_bound AS col3, MAX(col4) AS col4, MAX(col5) AS col5 FROM (query above) decorated_table GROUP BY col1, lower_bound, upper_bound

    你得到的正是你想要的结果。

    回到困难的部分:上面提到的帖子公开了 PostgreSQL 的解决方案。 MySQL 没有范围类型,但可以调整解决方案。例如,而不是 lower(range) ,直接使用下界col2 。该解决方案还利用了窗口函数,即 laglead ,但 MySQL 支持,with the same syntax ,所以这里没有问题。另请注意,他们使用 COALESCE(upper(range), 'infinity')以防止未绑定(bind)的范围。由于你的范围是有限的,你不需要关心这个,你可以直接使用上限,即 col3 。这是改编:

    WITH a AS (
       SELECT
           col2,
           col3,
           col2 AS lower_bound, 
           MAX(col3) OVER (ORDER BY col2, col3) AS upper_bound
       FROM   combine
       )
    , b AS (
       SELECT *, lag(upper_bound) OVER (ORDER BY col2, col3) < lower_bound OR NULL AS step
       FROM   a
       )
    , c AS (
       SELECT *, count(step) OVER (ORDER BY col2, col3) AS grp
       FROM   b
       )
    SELECT
        MIN(lower_bound) AS lower_bound,
        MAX(upper_bound) AS range
    FROM   c
    GROUP  BY grp
    ORDER  BY 1;
    

    这适用于单个组。如果你想通过 col1 获取范围,你可以这样调整:

    WITH a AS (
       SELECT
           col1,
           col2,
           col3,
           col2 AS lower_bound, 
           MAX(col3) OVER (PARTITION BY col1 ORDER BY col2, col3) AS upper_bound
       FROM   combine
       )
    , b AS (
       SELECT *, lag(upper_bound) OVER (PARTITION BY col1 ORDER BY col2, col3) < lower_bound OR NULL AS step
       FROM   a
       )
    , c AS (
       SELECT *, count(step) OVER (PARTITION BY col1 ORDER BY col2, col3) AS grp
       FROM   b
       )
    SELECT
        MIN(lower_bound) AS lower_bound,
        MAX(upper_bound) AS range
    FROM   c
    GROUP  BY col1, grp
    ORDER  BY 1;
    

    结合所有内容,我们得到以下结果(在您提供的示例上进行测试),返回的输出正是您期望的:

    WITH a AS (
       SELECT
           col1,
           col2,
           col3,
           col2 AS lower_bound, 
           MAX(col3) OVER (PARTITION BY col1 ORDER BY col2, col3) AS upper_bound
       FROM   combine
       )
    , b AS (
       SELECT *, lag(upper_bound) OVER (PARTITION BY col1 ORDER BY col2, col3) < lower_bound OR NULL AS step
       FROM   a
       )
    , c AS (
       SELECT *, count(step) OVER (PARTITION BY col1 ORDER BY col2, col3) AS grp
       FROM   b
       )
    , large_intervals AS (
        SELECT
            col1,
            MIN(lower_bound) AS lower_bound,
            MAX(upper_bound) AS upper_bound
        FROM   c
        GROUP  BY col1, grp
        ORDER  BY 1
        )
    , combine_with_large_interval AS (
        SELECT
            combine.*,
            large_intervals.lower_bound,
            large_intervals.upper_bound
        FROM combine
        INNER JOIN large_intervals
            ON large_intervals.col1 = combine.col1
            AND large_intervals.lower_bound <= combine.col2
            AND large_intervals.upper_bound >= combine.col3
    )
    SELECT
        col1,
        lower_bound AS col2,
        upper_bound AS col3, 
        MAX(col4) AS col4, 
        MAX(col5) AS col5
    FROM combine_with_large_interval
    GROUP BY col1, lower_bound, upper_bound
    ORDER BY col1, col2, col3;
    

    瞧!

    关于MySQL:如何将范围分区组合成最大可能的连续范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52190794/

    相关文章:

    php - 从另一台服务器远程连接到数据库

    mysql - bitnami 服务器 AWS EC2 micro 上的性能不佳

    C# MySQL DataRow 具有多个值

    php - Sphinx 搜索不理解特殊字符(重音符号)

    java - Hibernate 多对多映射附加列时间戳

    sql - MYSQL join - 从嵌套选择引用外部字段?

    javascript - 更改表中选定的选项

    mysql - HAVING 是按行还是按组运行?

    php - 通过Yii中的多对多关系查找不相关的记录

    mysql - SQL - 选择与 ID 关联的名称