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

sco*_*ttm 11 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)
Run Code Online (Sandbox Code Playgroud)

tpd*_*pdi 3

我认为 MarkusQ 已经找到了答案,但让我开发一个您可能会发现更容易使用的替代方案。我将使用我的惯用方法将其开发为视图中的一系列简单转换,类似于过程语言中的 功能分解。

首先,让我们将所有内容都放在通用单位中。回想一下,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,
;
Run Code Online (Sandbox Code Playgroud)

我们从中减去 1 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
;
Run Code Online (Sandbox Code Playgroud)

现在我们创建一个分钟表。我们需要 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;
Run Code Online (Sandbox Code Playgroud)

因此创建一个minute表:

  create view minute as 
   select id - 1 as active_minute 
   from artificial_range 
   where id <= 1440
   ;
Run Code Online (Sandbox Code Playgroud)

现在我们只需加入minute我们的记录视图

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 
 ;
Run Code Online (Sandbox Code Playgroud)

这只是交叉乘积/乘以记录行,因此呼叫处于活动状态的每一整分钟我们都有一个记录行。

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

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

现在,如果我们想要查找等于或大于分钟粒度的任何粒度的活动记录数,我们只需对最后一个视图进行分组。为了找到每小时的平均呼叫次数,我们将其除以 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 ) ;
Run Code Online (Sandbox Code Playgroud)

请注意,这是所有天中所有呼叫的平均每小时;如果我们想将其限制为特定的一天或几天范围,我们可以添加一个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) 
 ;
Run Code Online (Sandbox Code Playgroud)

然后我们再次进行选择,但针对新视图:

 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....
Run Code Online (Sandbox Code Playgroud)

我们还可以使用 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 ) ;
Run Code Online (Sandbox Code Playgroud)

在没有调用发生的几个小时内不会返回任何行。如果我们仍然想要显示所有时间的“类似报告”视图,我们确保还包括那些没有记录的时间:

 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 ) ;
Run Code Online (Sandbox Code Playgroud)

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

这就引出了另一点:记录表中的日期和时间都是多余的并且是非规范化的,因为每个日期和时间都可以从您的列中派生出来。将它们保留在表中可能会出现不一致的行,其中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
  ;
Run Code Online (Sandbox Code Playgroud)

函数中dateaddss是一个枚举类型,告诉函数加秒;s是记录中的列。