sql - 在数据库中查找不同时间之间的同时发生的事件

标签 sql sql-server

我有一个存储电话通话记录的数据库。每条通话记录都有开始时间和结束时间。我想知道同时发生的最大电话数量是多少,以便了解我们是否超出了电话银行中的可用电话线路数量。我该如何解决这个问题?

最佳答案

免责声明:我根据以下(优秀的)帖子编写我的答案:

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (也推荐第1部分和第2部分)

对于这个问题,首先要了解的是,互联网上当前找到的大多数解决方案基本上都存在两个问题

  • 结果不是正确答案(例如,如果范围 A 与 B 和 C 重叠,但 B 与 C 不重叠,则它们会被视为 3 个重叠范围)。
  • 计算它的方法非常低效(因为 O(n^2) 和/或它们在周期内每秒循环)

Unreasons 提出的解决方案中常见的性能问题是一个二次解决方案,对于每个调用,您需要检查所有其他调用是否重叠。

有一个算法线性通用解决方案,即按日期排序列出所有“事件”(开始通话和结束通话),开始加 1,挂断减 1,并记住最大值。这可以通过游标轻松实现(Hafhor 提出的解决方案似乎就是这样),但游标并不是解决问题的最有效方法。

引用的文章有很好的例子,不同的解决方案,以及它们的性能比较。建议的解决方案是:

WITH C1 AS
(
  SELECT starttime AS ts, +1 AS TYPE,
    ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
  FROM Calls

  UNION ALL

  SELECT endtime, -1, NULL
  FROM Calls
),
C2 AS
(
  SELECT *,
    ROW_NUMBER() OVER(  ORDER BY ts, TYPE) AS start_or_end_ordinal
  FROM C1
)
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
<小时/>

说明

假设这组数据

+-------------------------+-------------------------+
|        starttime        |         endtime         |
+-------------------------+-------------------------+
| 2009-01-01 00:02:10.000 | 2009-01-01 00:05:24.000 |
| 2009-01-01 00:02:19.000 | 2009-01-01 00:02:35.000 |
| 2009-01-01 00:02:57.000 | 2009-01-01 00:04:04.000 |
| 2009-01-01 00:04:12.000 | 2009-01-01 00:04:52.000 |
+-------------------------+-------------------------+

这是一种用查询实现相同想法的方法,每次调用开始加 1,每次结束减 1。

  SELECT starttime AS ts, +1 AS TYPE,
    ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
  FROM Calls

C1 CTE 的这一部分将获取每个调用的每个开始时间并对其进行编号

+-------------------------+------+---------------+
|           ts            | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 |    1 |             1 |
| 2009-01-01 00:02:19.000 |    1 |             2 |
| 2009-01-01 00:02:57.000 |    1 |             3 |
| 2009-01-01 00:04:12.000 |    1 |             4 |
+-------------------------+------+---------------+

现在这段代码

  SELECT endtime, -1, NULL
  FROM Calls

将生成所有没有行编号的“结束时间”

+-------------------------+----+------+
|         endtime         |    |      |
+-------------------------+----+------+
| 2009-01-01 00:02:35.000 | -1 | NULL |
| 2009-01-01 00:04:04.000 | -1 | NULL |
| 2009-01-01 00:04:52.000 | -1 | NULL |
| 2009-01-01 00:05:24.000 | -1 | NULL |
+-------------------------+----+------+

现在使 UNION 具有完整的 C1 CTE 定义,您将混合两个表

+-------------------------+------+---------------+
|           ts            | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 |    1 |             1 |
| 2009-01-01 00:02:19.000 |    1 |             2 |
| 2009-01-01 00:02:57.000 |    1 |             3 |
| 2009-01-01 00:04:12.000 |    1 |             4 |
| 2009-01-01 00:02:35.000 | -1   |     NULL      |
| 2009-01-01 00:04:04.000 | -1   |     NULL      |
| 2009-01-01 00:04:52.000 | -1   |     NULL      |
| 2009-01-01 00:05:24.000 | -1   |     NULL      |
+-------------------------+------+---------------+

C2 是使用新列对 C1 进行排序和编号计算得出的

C2 AS
(
  SELECT *,
    ROW_NUMBER() OVER(  ORDER BY ts, TYPE) AS start_or_end_ordinal
  FROM C1
)

+-------------------------+------+-------+--------------+
|           ts            | TYPE | start | start_or_end |
+-------------------------+------+-------+--------------+
| 2009-01-01 00:02:10.000 |    1 | 1     |            1 |
| 2009-01-01 00:02:19.000 |    1 | 2     |            2 |
| 2009-01-01 00:02:35.000 |   -1 | NULL  |            3 |
| 2009-01-01 00:02:57.000 |    1 | 3     |            4 |
| 2009-01-01 00:04:04.000 |   -1 | NULL  |            5 |
| 2009-01-01 00:04:12.000 |    1 | 4     |            6 |
| 2009-01-01 00:04:52.000 |   -1 | NULL  |            7 |
| 2009-01-01 00:05:24.000 |   -1 | NULL  |            8 |
+-------------------------+------+-------+--------------+

神奇之处在于,任何时候#start - #ends 的结果就是此时的并发调用量。

对于每个 Type = 1(开始事件),我们在第三列中有 #start 值。我们还有#start + #end(在第四列)

#start_or_end = #start + #end

#end = (#start_or_end - #start)

#start - #end = #start - (#start_or_end - #start)

#start - #end = 2 * #start - #start_or_end

SQL 语句如下:

SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1

在本例中,使用建议的调用集,结果为 2。

在提议的文章中,有一点改进可以通过例如服务或“电话公司”或“电话中心”来分组结果,并且这个想法也可以用于例如按时隙进行分组并具有给定一天中每小时的最大并发数。

关于sql - 在数据库中查找不同时间之间的同时发生的事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3044764/

相关文章:

sql - Postgresql 使用聚合函数时列的最后一个值

sql - 限制加入一行

php - SQL,查询电影院

c# - .NET MVC 脚本反复用于删除 SQL Server 数据库以防止数据库连接

SQL Server - 如果两个特定字段都为空,则从 View 中排除记录

Mysql 从 LEFT OUTER JOIN 中排除记录

mysql 查询 - 没有得到它应该提供的结果

php - 将 MSSQL 中的所有非数字字符替换为空字符串

sql-server - Flutter 使用 Azure API 连接到 SQL SERVER DB

sql - 为什么 `NOT(NULL=NULL)` 是假的?