sql - 如何根据事件的日期、时间和持续时间检查 SQL 表中的平均并发事件?

标签 sql sql-server algorithm sql-server-2000 sql-server-7

我有一组调用详细记录,根据这些记录,我应该确定每个系统每小时(精确到一分钟)的平均并发事件调用。如果我查询晚上 7 点到晚上 8 点,我应该看到该小时内(对于每个系统)该小时的平均并发调用(平均每分钟的并发调用)。

因此,我需要一种方法来检查 7:00-7:01、7:01-7:02 等时间段的事件调用计数,然后对这些数字进行平均。如果调用的时间和持续时间落在正在检查的当前分钟内,则调用被视为有效。

使这变得更加困难的是它需要跨越 SQL 7.0 和 SQL 2000(2000 中的某些函数在 7.0 中不可用,例如 GetUTCTime()),如果我能让 2000 工作,我会快乐。

我可以采取什么方法来解决这个问题?

我考虑过循环检查一小时内的分钟 (60) 并添加那一分钟之间的调用计数,然后以某种方式交叉引用持续时间以确保调用从晚上 7:00 开始并300 秒的持续时间显示在 7:04 处于事件状态,但我无法想象如何解决该问题。我试图找出一种方法来根据特定分钟对每个调用进行加权,以告诉我调用在那一分钟内是否处于事件状态,但无法提出有效的解决方案。

此处的数据类型与我必须查询的数据类型相同。我对架构没有任何控制权(除了可能转换数据并插入到另一个具有更合适数据类型的表中)。我提供了一些我知道有并发事件调用的示例数据。

CREATE TABLE Records(
  seconds char(10),
  time char(4),
  date char(8),
  dur int,
  system int,
  port int,
)

--seconds is an stime value. It's the difference of seconds from UTC 1/1/1970 00:00:00 to the current UTC time, we use it as an identifier (like epoch).
--time is the time the call was made.
--date is the day the call was made.
--dur is the duration of the call in seconds.
--system is the system number.
--port is the port on the system (not particularly relevant for this question).

INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924228','1923','20090416',105,2,2)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923455','1910','20090416',884,1,97)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924221','1923','20090416',116,2,15)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924259','1924','20090416',90,1,102)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923458','1910','20090416',891,2,1)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924255','1924','20090416',99,2,42)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924336','1925','20090416',20,2,58)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924293','1924','20090416',64,2,41)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923472','1911','20090416',888,2,27)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924347','1925','20090416',25,1,100)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924301','1925','20090416',77,2,55)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924332','1925','20090416',52,2,43)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924240','1924','20090416',151,1,17)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924313','1925','20090416',96,2,62)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924094','1921','20090416',315,2,16)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923643','1914','20090416',788,2,34)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924447','1927','20090416',6,2,27)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924342','1925','20090416',119,2,15)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924397','1926','20090416',76,2,41)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924457','1927','20090416',23,2,27)

最佳答案

我认为 MarkusQ 有答案,但让我开发一个您可能会发现更易于使用的替代方案。我会用我的 customary method将其开发为一系列简单的 View 转换,类似于 functional decomposition在程序语言中。

首先,让我们将所有内容都放在通用单位中。回想一下 record的专栏s是自 1970 年 1 月 1 日午夜 纪元 以来的秒数。我们可以找到自调用当天午夜以来的秒数,该调用发生,只需将 s 取模一天中的秒数: s % (60 * 60 * 24) .

select *, 
s % (60 * 60 * 24) as start_secs_from_midnight,
s % (60 * 60 * 24) + dur - 1 as end_secs_from_midnight,
;

我们从s + dur中减一因为从 12:00:00 开始的一秒钟调用也将在 12:00:00 结束。

我们可以通过将这些结果除以 60 或仅除以 floor( s / 60 ) % (60 * 24) 来找到自午夜以来的分钟数:

create view record_mins_from_midnight as
select *, 
floor( s / 60 ) % (60 * 24) as start_mins_fm,
floor( ( s + dur - 1) / 60 ) % (60 * 24) as end_mins_fm 
from record
;

现在我们创建一个 session 记录表。我们需要其中的 1440 个,编号从 0 到 1439。在不支持任意序列的数据库中,我创建一个人工范围或序列,如下所示:

  create table artificial_range ( 
   id int not null primary key auto_increment, idz int) ;
  insert into artificial_range(idz) values (0);
  -- repeat next line to double rows
  insert into artificial_range(idz) select idz from artificial_range;

所以要创建一个minute表:

  create view minute as 
   select id - 1 as active_minute 
   from artificial_range 
   where id <= 1440
   ;

现在我们加入minute到我们的记录 View

create view record_active_minutes as
select * from minutes a 
join record_mins_from_midnight b
on (a.active_minute >= b.start_mins_fm 
and a.active_minute <= b.end_mins_fm 
 ;

这只是交叉乘积/乘以记录行,所以我们有一个记录行对应通话活跃的每一分钟。

请注意,我通过将事件定义为“(部分)通话在一分钟内发生”来实现这一点。也就是说,根据此定义,从 12:00:59 开始到 12:01:01 结束的两秒调用发生在不同的两分钟内,但是从 12:00:58 开始到 12 点结束的两秒调用: 00:59 在一分钟内出现。

我这样做是因为您指定了“所以,我需要一种方法来检查 7:00-7:01、7:01-7:02 的事件调用计数”。如果您希望仅考虑持续时间超过 60 秒的调用在超过一分钟内发生,则需要调整连接。

现在,如果我们想要查找等于或大于分钟粒度的任何粒度的事件记录数,我们只需对最后一个 View 进行分组。为了找到每小时的平均通话次数,我们将分钟数除以 60,将分钟数换算为小时数:

 select floor( active_minute / 60 ) as hour, 
 count(*) / 60 as avg_concurent_calls_per_minute_for_hour
 from record_active_minutes
 group by floor( active_minute / 60 ) ;

请注意,这是所有天所有通话每小时的平均值;如果我们想将它限制在特定的一天或几天的范围内,我们会添加一个 where条款。


但是等等,还有更多!

如果我们创建 record_active_minutes 的版本执行左外部连接,我们可以获得显示一天中所有时间的平均值的报告:

 create view record_active_minutes_all as
 select * 
 from 
 minutes a 
 left outer join record_mins_from_midnight b
   on (a.active_minute >= b.start_mins_fm 
       and a.active_minute <= b.end_mins_fm) 
 ;

然后我们再次进行选择,但针对的是新 View :

 select floor( active_minute / 60 ) as hour, 
 count(*) / 60 as avg_concurent_calls_per_min
 from record_active_minutes_all
 group by floor( active_minute / 60 ) ;


+------+------------------------------+
| hour | avg_concurrent_calls_per_min |
+------+------------------------------+
|    0 |                       0.0000 |
|    1 |                       0.0000 |
|    2 |                       0.0000 |
|    3 |                       0.0000 |
   etc....

我们也可以用 where 来索引它。不幸的是,连接意味着我们将有底层 record 的空值。特定时间不存在调用的表,例如,

 select floor( active_minute / 60 ) as hour, 
 count(*) / 60 as avg_concurent_calls_per_min
 from record_active_minutes_all
 where month(date) = 1 and year(date) = 2008 
 group by floor( active_minute / 60 ) ;

在没有调用发生的时间段内不会返回任何行。如果我们仍然希望我们的“类似报告”的 View 显示所有时间,我们确保我们也包括那些没有记录的时间:

 select floor( active_minute / 60 ) as hour, 
 count(*) / 60 as avg_concurent_calls_per_minute_for_hour
 from record_active_minutes_all
 where (month(date) = 1 and year(date) = 2008) 
 or date is null 
 group by floor( active_minute / 60 ) ;

请注意,在最后两个示例中,我使用的是 SQL 日期(可以应用函数 monthyear),而不是记录表中的 char(4) 日期。

这引出了另一点:记录表中的日期和时间都是多余的和非规范化的,因为每个都可以从您的 column 中派生。将它们留在表中可能会出现不一致的行,其中 date(s) <> datetime(s) <> time .我更愿意这样做:

   create table record ( id int not null primary key, s, duration) ; 

   create view record_date as 
   select *, dateadd( ss, s, '1970-01-01') as call_date
   from record
  ;

dateadd功能,ss是一个枚举类型,告诉函数添加秒数; s是记录中的列。

关于sql - 如何根据事件的日期、时间和持续时间检查 SQL 表中的平均并发事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/761700/

相关文章:

algorithm - 在文档中查找个人信息(难题)

algorithm - 管理每个用户的访问日志的最佳方式是什么?

java - 清理自动生成的 SQL 架构的最佳方法

sql - 条件总和的左连接横向

sql - 参数化 SQL 和 NULL 运行缓慢

mysql - MySQL 中何时使用单引号、双引号和反引号

sql-server - SQL Server 拆分和表插入

sql server 从日期获取完整的月份名称

sql - 从 SQL 登录中提取密码

python - 为什么我的前 k 个常见问题 [LEETCODE] 超出了限制?