【问题标题】:Merging intervals in one pass in SQL在 SQL 中一次性合并间隔
【发布时间】:2011-12-09 21:30:06
【问题描述】:

假设我有一个包含两列的表:startend,都是整数,并且该表按第一列排序,然后是第二列。每行代表一个区间。

我需要的是合并区间表:所有重叠或相邻的区间合并为一个。

它可以用 JOIN 查询来构造,但行数是二次方的,在我的例子中是 400 万行(我决定编写这个问题,因为查询仍在运行)。

它也可以在单次中完成,通过运行每一行并跟踪最大结束时间——但是如何在标准 SQL 中做到这一点,或者类似的东西?在 SQL 中有 any O(n) 方法吗?我现在正在使用 SQLite;这一次,一个特定于 SQLite 的解决方案也会帮助我。

从相关问题的答案(123456789)我不能告诉它是否可能。

你可以吗?

【问题讨论】:

  • 我可以想办法使用公用表表达式或递归查询来完成此任务,但 SQLite 不支持这些功能。 PostgreSQL 确实如此 :)
  • 速度是否胜过一切?为了速度,临时表或其他东西可以吗?
  • 什么是最小可能的“开始”和最大可能的“结束”?或者你的情况根本没有限制?这些值是否有已知限制? (即使没有实际用于表中的区间)
  • 使用临时表很好。第一个开始和最后一个结束相距约400万个单位(巧合),而同一行内的最大差异通常为1或2,但峰值为1000。
  • sqlite 允许您在主机编程语言中创建用户定义的函数。考虑到您可以创建聚合函数,您可以一次性完成。但是,考虑到嵌入了 sqlite,我不确定这比仅获取所有数据并在宿主语言中使用循环要好多少。

标签: sql sqlite overlapping intervals


【解决方案1】:

嗯,这是一个适用于 MySQL 的解决方案(我不知道它是否适用于 SQlite)。我认为,但不能证明,这是 O(n) (放弃最初对事件表进行排序所花费的时间,即如果它已经按照我认为的问题所述进行排序。)

> SELECT * from events;
+-------+-----+
| start | end |
+-------+-----+
|     1 |   9 |
|     5 |   8 |
|     8 |  11 |
|    11 |  13 |
|    17 |  25 |
|    18 |  26 |
|    33 |  42 |
|    59 |  81 |
|    61 |  87 |
|    97 | 132 |
|   105 | 191 |
|   107 | 240 |
|   198 | 213 |
|   202 | 215 |
+-------+-----+
14 rows in set (0.00 sec)


SET @interval_id = 0;
SET @interval_end = 0;

SELECT
  MIN(start) AS start,
  MAX(end) AS end
  FROM
    (SELECT
       @interval_id := IF(start > @interval_end,
                          @interval_id + 1,
                          @interval_id) AS interval_id,
       @interval_end := IF(start < @interval_end,
                           GREATEST(@interval_end, end),
                           end) AS interval_end,
       events.*
     FROM events
     ORDER BY start,end) tmp
  GROUP BY interval_id;

+-------+------+
| start | end  |
+-------+------+
|     1 |   13 |
|    17 |   26 |
|    33 |   42 |
|    59 |   87 |
|    97 |  240 |
+-------+------+
5 rows in set (0.00 sec)

【讨论】:

  • 是的,已排序。我不知道你可以在 MySQL 中做到这一点。
【解决方案2】:

在您的链接中,您省略了一个:Can I use a SQL Server CTE to merge intersecting dates?,我在其中为重叠间隔问题提供了 RECURSIVE CTE 解决方案。递归 CTE 可以以不同的方式处理(与普通的自连接相比),并且通常执行得非常快。

mysql 没有递归 CTE。 Postgres 有,Oracle 有,Microsoft 有。

这里Querying for a 'run' of consecutive columns in Postgres 是另一个,带有软糖因素。

这里Get total time interval from multiple rows if sequence not broken 是另一个。

【讨论】:

  • 我不知道 - 谢谢(不幸的是。SQLite 不支持它们:stackoverflow.com/questions/7456957/…
  • 这是否比 A.J. 的方法更有效,如果是,为什么?
  • A.J. 的方法不使用第一个 KEY 组件,仅使用 min(start) 结束。通常这将是“select id, min(ddate)”(或 max (ddate))GROUP BY id。 @reinierpost:它不仅效率更高,而且是正确的;-)
  • 我提到的方法一开始并不完整。这只是为了最小化构成合并解决方案起点的行的第一步。该方法针对的是您的解决方案中提到的概念不可用的数据库,例如当前使用 SQLite 的情况。
【解决方案3】:

根据我在 cmets 中对我的问题的回答,我认为我的想法不会奏效。既然你提到它可以(我假设你知道如何)通过连接来完成,我想到了通过只保留属于不同点的范围来最小化要连接的行数,如下所示:

select start, max(end) as end
from (
      select min(start) as start,end
      from table
      group by end
     ) in_tab
group by in_tab.start

上面的内部选择确保没有终点重复,并为每个终点选择最长的起点。外部选择正好相反。我们最终得到在不同点开始和结束的范围(删除了任何完全包含/重叠的范围)。 如果最大范围不大,这可能会起作用。如果这些是日期,并且整个表格中的最低日期和其中的最高日期之间存在最大年份差异,那么选择任意两个点的选项将是 365*364,这将是可能行的上限经过上面的选择。然后可以使用您已经拥有的连接方法在临时表中使用这些。但是根据您提到的数字,理论上我们有一个巨大的数字,这使得这次尝试无关紧要。即使上述方法最小化了要在计算中使用的行,它们仍然太多而无法在连接中使用。

当 RDBMS 没有提供其他非标准功能时,我不知道在没有连接的情况下在 ANSI SQL 中进行此操作的方法。例如,在 oracle 中,这可以通过分析功能轻松实现。在这种情况下,最好的办法是使用上述方法来最小化使用的行数并将它们带到您的应用程序中,您可以在那里编写计算范围的代码并将它们插入回数据库中。

【讨论】:

  • 我已经想到了你的方法,我认为它会比连接更有效(每次开始的结束数,或每次结束的开始数,比总行数小得多) .我可能需要添加索引。所以它已经完成了一半。
  • 问题是仅仅获得一次 MIN 和 MAX 是不够的。区间 (1,4), (2,7), (5,9) 将被合并,即使 2 的 MAX(end) 是 7 并且 7 的 MIN(start) 是 2。
  • 你是对的。但我在文本中提到,选择只是为了最小化要处理的行。这意味着,在上述查询之后,您仍然需要进行连接以进行最终合并。 (在您提到的原始文本中,您可以通过加入来完成)。我想要的只是最小化您在连接中使用的行数,所以我做的是第一步(我写道,结果可以存储在临时表中,然后将您的方法应用于它们)。
  • 是的,我也想过这个想法,但是在浏览了数据之后,确定它不会对我的情况产生太大影响。
【解决方案4】:

目前,我找到的最佳答案是:使用索引。 这将复杂度从二次降低到 O(n log n)。

使用covering index,查询速度足以满足我的需求; 在开始列或结束列上只有一个索引,速度较慢但仍然可以。 在每种情况下,EXPLAIN QUERY PLAN 都告诉我单表扫描与索引的使用相结合,正如预期的那样。

在索引中找到一个元素并不是 O(1),但结果证明已经足够接近了。 而且构建索引也不慢。

剩下的就是证明真正的 O(n) 算法不能用 SQL 编写。

所以另一个答案是用不同的语言编写它,然后将其应用于 SQLite 表。 有多种方法可以做到这一点:

  • 将表格导出为 CSV 文件;读取 CSV 文件,应用算法,生成 CSV;将生成的 CSV 文件导入为表格;
  • 为该语言使用 SQLite 驱动程序(例如,DBD::SQLite 用于 Perl,RSQLite 用于 R)
  • 编写一个 SQLite 扩展函数,以某种方式与所选语言交互

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-19
    • 2012-01-15
    • 2014-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多