sql - 展平相交的时间跨度

标签 sql sql-server sql-server-2005 algorithm datetime

我有大量包含给定 ID 的开始和停止时间的数据,我需要将所有相交和相邻的时间跨度展平为一个组合时间跨度。下面发布的示例数据都是针对同一个 ID,所以我没有列出。

为了让事情更清楚一点,请看一下 03.06.2009 的示例数据:

以下时间跨度重叠或连续,需要合并为一个时间跨度

  • 05:54:48 - 10:00:13
  • 09:26:45 - 09:59:40

生成的时间跨度将从 05:54:48 到 10:00:13。由于 10:00:13 和 10:12:50 之间存在间隔,我们还有以下时间跨度:

  • 10:12:50 - 10:27:25
  • 10:13:12 - 11:14:56
  • 10:27:25 - 10:27:31
  • 10:27:39 - 13:53:38
  • 11:14:56 - 11:15:03
  • 11:15:30 - 14:02:14
  • 13:53:38 - 13:53:43
  • 14:02:14 - 14:02:31

这导致从 10:12:50 到 14:02:31 的一个合并时间跨度,因为它们重叠或相邻。

您将在下面找到我需要的示例数据和扁平化数据。持续时间列只是提供信息。

任何解决方案——无论是否是 SQL——都值得赞赏。


编辑:由于有许多不同且有趣的解决方案,我通过添加约束来改进我原来的问题,以查看“最佳”(如果有的话)解决方案:

  • 我正在通过 ODBC 从另一个系统获取数据。无法为我更改表格布局或添加索引
  • 数据仅由日期列索引(时间部分不是)
  • 每天大约有 2.5k 行
  • 数据的预估使用模式大致如下:
    • 大多数时候(比方说 90%),用户只会查询一两天(2.5k - 5k 行)
    • 有时 (9%) 范围会长达一个月(约 75,000 行)
    • 很少 (1%) 范围会长达一年(约 90 万行)
  • 查询对于典型情况应该是快速的,而不是对于极少数情况“永远持续”。
  • 查询一年的数据大约需要 5 分钟(没有连接的纯选择)

在这些限制条件下,最佳解决方案是什么?恐怕大多数解决方案都会非常慢,因为它们加入了日期和时间的组合,在我的例子中这不是索引字段。

您会在客户端还是服务器端进行所有合并?您会先创建一个优化的临时表并对该表使用建议的解决方案之一吗?直到现在我都没有时间测试这些解决方案,但我会随时通知您最适合我的解决方案。


示例数据:

Date       | Start    | Stop
-----------+----------+---------
02.06.2009 | 05:55:28 | 09:58:27
02.06.2009 | 10:15:19 | 13:58:24
02.06.2009 | 13:58:24 | 13:58:43
03.06.2009 | 05:54:48 | 10:00:13
03.06.2009 | 09:26:45 | 09:59:40
03.06.2009 | 10:12:50 | 10:27:25
03.06.2009 | 10:13:12 | 11:14:56
03.06.2009 | 10:27:25 | 10:27:31
03.06.2009 | 10:27:39 | 13:53:38
03.06.2009 | 11:14:56 | 11:15:03
03.06.2009 | 11:15:30 | 14:02:14
03.06.2009 | 13:53:38 | 13:53:43
03.06.2009 | 14:02:14 | 14:02:31
04.06.2009 | 05:48:27 | 09:58:59
04.06.2009 | 06:00:00 | 09:59:07
04.06.2009 | 10:15:52 | 13:54:52
04.06.2009 | 10:16:01 | 13:24:20
04.06.2009 | 13:24:20 | 13:24:24
04.06.2009 | 13:24:32 | 14:00:39
04.06.2009 | 13:54:52 | 13:54:58
04.06.2009 | 14:00:39 | 14:00:49
05.06.2009 | 05:53:58 | 09:59:12
05.06.2009 | 10:16:05 | 13:59:08
05.06.2009 | 13:59:08 | 13:59:16
06.06.2009 | 06:04:00 | 10:00:00
06.06.2009 | 10:16:54 | 10:18:40
06.06.2009 | 10:18:40 | 10:18:45
06.06.2009 | 10:23:00 | 13:57:00
06.06.2009 | 10:23:48 | 13:57:54
06.06.2009 | 13:57:21 | 13:57:38
06.06.2009 | 13:57:54 | 13:57:58
07.06.2009 | 21:59:30 | 01:58:49
07.06.2009 | 22:12:16 | 01:58:39
07.06.2009 | 22:12:25 | 01:58:28
08.06.2009 | 02:10:33 | 05:56:11
08.06.2009 | 02:10:43 | 05:56:23
08.06.2009 | 02:10:49 | 05:55:59
08.06.2009 | 05:55:59 | 05:56:01
08.06.2009 | 05:56:11 | 05:56:14
08.06.2009 | 05:56:23 | 05:56:27

扁平化结果:

Date       | Start    | Stop     | Duration
-----------+----------+----------+---------
02.06.2009 | 05:55:28 | 09:58:27 | 04:02:59
02.06.2009 | 10:15:19 | 13:58:43 | 03:43:24
03.06.2009 | 05:54:48 | 10:00:13 | 04:05:25
03.06.2009 | 10:12:50 | 14:02:31 | 03:49:41
04.06.2009 | 05:48:27 | 09:59:07 | 04:10:40
04.06.2009 | 10:15:52 | 14:00:49 | 03:44:58
05.06.2009 | 05:53:58 | 09:59:12 | 04:05:14
05.06.2009 | 10:16:05 | 13:59:16 | 03:43:11
06.06.2009 | 06:04:00 | 10:00:00 | 03:56:00
06.06.2009 | 10:16:54 | 10:18:45 | 00:01:51
06.06.2009 | 10:23:00 | 13:57:58 | 03:34:58
07.06.2009 | 21:59:30 | 01:58:49 | 03:59:19
08.06.2009 | 02:10:33 | 05:56:27 | 03:45:54

最佳答案

这是一个只有 SQL 的解决方案。我对列使用了 DATETIME。在我看来,将时间分开存储是一个错误,因为当时间超过午夜时你会遇到问题。如果需要,您可以调整它来处理这种情况。该解决方案还假定开始时间和结束时间不为空。同样,如果不是这种情况,您可以根据需要进行调整。

解决方案的一般要点是获取所有不与任何其他跨度重叠的开始时间,获取所有不与任何跨度重叠的结束时间,然后将两者匹配在一起。

除了在一种情况下,结果与您的预期结果相符,手动检查看起来您的预期输出有误。 6 号应该有一个结束于 2009-06-06 10:18:45.000 的跨度。

SELECT
     ST.start_time,
     ET.end_time
FROM
(
     SELECT
          T1.start_time
     FROM
          dbo.Test_Time_Spans T1
     LEFT OUTER JOIN dbo.Test_Time_Spans T2 ON
          T2.start_time < T1.start_time AND
          T2.end_time >= T1.start_time
     WHERE
          T2.start_time IS NULL
) AS ST
INNER JOIN
(
     SELECT
          T3.end_time
     FROM
          dbo.Test_Time_Spans T3
     LEFT OUTER JOIN dbo.Test_Time_Spans T4 ON
          T4.end_time > T3.end_time AND
          T4.start_time <= T3.end_time
     WHERE
          T4.start_time IS NULL
) AS ET ON
     ET.end_time > ST.start_time
LEFT OUTER JOIN
(
     SELECT
          T5.end_time
     FROM
          dbo.Test_Time_Spans T5
     LEFT OUTER JOIN dbo.Test_Time_Spans T6 ON
          T6.end_time > T5.end_time AND
          T6.start_time <= T5.end_time
     WHERE
          T6.start_time IS NULL
) AS ET2 ON
     ET2.end_time > ST.start_time AND
     ET2.end_time < ET.end_time
WHERE
     ET2.end_time IS NULL

关于sql - 展平相交的时间跨度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/964288/

相关文章:

mysql - 选择最后一天时间范围为早上 7 点到午夜之间的所有 MySQL 记录

php - 在 PHP 中循环并更新 sql 表的每一行

mysql - Sql:“哪里”和“与”

mysql - 如何找出sql server 2005中各个数据库的大小?

sql - 列名中应该使用下划线吗?

MySQL 通过每秒从另外 2 个列中选择来更新一个列

sql-server - SQL Server 和 Laravel : Invalid Object Name error

sql-server - 如何查询特定用户访问权限

sql-server - Windows Server 2016 中的访问数据库引擎 OLEDB

SQL 这些是一样的吗?