3

我在 SQL Server 数据库中有一个事件日志。本质上,它会记录拨打电话的时间以及电话在呼叫中心结束的时间(作为两个不同的记录),以及其他一些细节。我试图通过这些数据了解在任何给定时间使用了多少电话线。我想不出任何让 SQL 查询为我确定这一点的好方法,尽管这将是理想的(如果它不牺牲太多速度的话)。

我的第一个想法是让程序查询每次通话的开始和结束事件,确定通话的持续时间。然后,我可以逐步检查每个时间单位,记录在任何给定时间正在进行的呼叫数量。有没有办法我可以在 SQL 中做到这一点,而不是在 C# 或类似的东西中使用线性方法?

编辑:呼叫有一个唯一的 ID。一个会话 ID,如果你愿意的话。此外,开始和结束事件是两条不同的记录——不是一条记录。这让我觉得有点复杂。此外,该表中有超过 1500 万条记录。

Id  EvId             CallId                           DateTime       
--  ---- ------------------------------------    --------------------
 1  0   df1cbc93-5cf3-402a-940b-4441f6a7ec5c     7/9/2008 8:12:56 PM
 2  1   df1cbc93-5cf3-402a-940b-4441f6a7ec5c     7/9/2008 8:13:07 PM
 3  0   ec1c2078-1765-4377-9126-6f26fe33e4a9    7/10/2008 4:33:10 PM
 4  10  ec1c2078-1765-4377-9126-6f26fe33e4a9    7/10/2008 4:33:13 PM
 5  1   ec1c2078-1765-4377-9126-6f26fe33e4a9    7/10/2008 4:33:13 PM
 6  0   a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d    7/10/2008 4:33:13 PM
 7  10  a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d    7/10/2008 4:33:15 PM
 8  1   a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d    7/10/2008 4:33:15 PM
 9  0   d23f393d-0272-445a-8670-3f71b016174e    7/10/2008 4:33:15 PM
10  10  d23f393d-0272-445a-8670-3f71b016174e    7/10/2008 4:33:17 PM
11  1   d23f393d-0272-445a-8670-3f71b016174e    7/10/2008 4:33:17 PM


EvId   Description
----   ----------------
  0 New Call
  1 End of Call
  2 Caller Hangup
 10 CPA Completed
4

4 回答 4

1

这是一个查询,它生成给定时间段内所有事件的日志,以及事件发生时的当前调用计数。它使用几个 CTE 以逻辑步骤顺序构建所需的数据:选择在开始时间之前开始的呼叫,减去在开始时间之前结束的呼叫,在开始时间和结束时间之间添加呼叫事件。然后使用此结果集生成事件时间线,以及任何事件的当前调用计数。使用 CTE 仅仅是因为我发现它们比派生表更容易阅读和理解。

declare @temp table (
    EvId int not null
    , CallId uniqueidentifier not null
    , DateTime Datetime not null);

 declare @starttime datetime
    , @endtime datetime;

 select @starttime = '7/10/2008 1:33:14 PM';
 select @endtime = '7/10/2008 1:43:14 PM';

 -- These are all the calls
 -- that started before the start time
 with started_call as (
 select * from call_log 
    where DateTime < @starttime 
    and EvId = 0)
-- These are all the calls 
-- that ended    before the start time
 , ended_call as (
 select * from call_log 
    where DateTime < @starttime 
    and EvId = 1)
-- These are all the call ids 
-- that were ongoing at the start time  
 , existing_calls as (
 select CallId from started_call
 except
 select CallId from ended_call)
-- These are all the call events logged
-- for calls that were were ongoing at the start time   
 , existing_details as (
 select l.* 
    from call_log l
    join existing_calls e on e.CallId = l.CallId
    where l.DateTime < @starttime)
-- these are events that occured
-- between start time and endtime   
, new_events as (
    select * from call_log
    where DateTime between @starttime and @endtime)
-- and these are all the events that are of interest
, all_events as (
    select * from existing_details
    union all
    select * from new_events)
-- put all the interesting events into a @temp table
-- unfortunately QO cannot spool this for us
-- so we better do it isntead   
insert into @temp (EvId, CallId, DateTime)
    select EvId, CallId, DateTime  from all_events;

-- Extract events, along with the count
-- at the time of the event
select e.*,(
        select sum(case
            when EvId = 0 then 1 -- Start call
            when EvId = 1 then -1 -- end call
            else 0 end) -- Other events 
        from @temp se
        where se.DateTime < e.DateTime) as cnt
from @temp e
where DateTime between @starttime and @endtime
order by DateTime;

如果存在适当的索引,此查询会生成一个不扫描整个日志表的计划。它为任何间隔提供正确的结果,并考虑到间隔开始时间的现有呼叫。在我对 1 百万条日志记录的测试中,它在 1.5GB RAM 单进程笔记本电脑上始终以 1.1 秒的时间间隔生成 10 分钟的事件(生成 @temp 表需要 628 毫秒,生成当前计数的时间线需要 505 毫秒)。如果引入对任何呼叫的最大持续时间的限制,则可以提高大型表的性能,因为在开始时间搜索现有呼叫可以限制在较低端(DatTime >= 开始时间 - 呼叫的最大持续时间) .

使用中间 @temp 表变量并不优雅,但很有效。

这是一个示例输出:

EvId    CallId                                  DateTime                cnt
1   401D9E00-040C-4B0E-8864-C66B72CF47AA    2008-07-10 13:33:16.000 23
10  401D9E00-040C-4B0E-8864-C66B72CF47AA    2008-07-10 13:33:16.000 23
1   8BF7AF50-B32C-464A-AF01-FDB653F0517D    2008-07-10 13:33:18.000 22
10  8BF7AF50-B32C-464A-AF01-FDB653F0517D    2008-07-10 13:33:18.000 22
0   CB523E24-5CE2-4E36-9D6C-4AE7BCEB1F53    2008-07-10 13:33:19.000 21
1   4A54EEB6-A899-4167-9D5C-2CE1BC838FFB    2008-07-10 13:33:20.000 22

这是我创建和加载测试数据的方式。注意表上的聚集索引和非聚集索引,它们都很关键。

create table call_log (id int identity(1,1) not null
    , EvId int not null
    , CallId uniqueidentifier not null
    , DateTime Datetime not null);
create clustered index cdx_call_log on call_log(EvId, DateTime);
create nonclustered index idx_call_log_call_id on call_log(CallId);
go

 set nocount on;
 declare @i int, @date datetime, @callId uniqueidentifier;
 select @i = 0, @date = '7/10/2008 12:33:14 PM';
 begin transaction
 while @i < 1000000
 begin
    declare @duration int,
        @delay int;
    select @duration = rand()*180,
        @delay = rand() * 10;
    select @date = dateadd(second, @delay, @date)
        , @callId = newid();

    insert into call_log (EvId, CallId, DateTime)
    values  (0, @callId, @date)
        , (10, @callId, dateadd(second, @duration, @date))
        , (1, @callId, dateadd(second, @duration, @date));
    select @i = @i + 1;
    if (0 = @i%100)
    begin
        commit;
        begin tran;
    end
 end
 commit
 go
于 2009-08-17T21:50:22.640 回答
1

在使用我的查询示例之前,您需要设置一个“帮助”表,每个数据库只需要这样做一次:

CREATE TABLE Numbers
(Number int  NOT NULL,
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
    SET @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

这基本上创建了一个表,其中包含一个包含从 1 到 8000 的值的列。您可以使用 CTE 来做同样的事情,但是由于您没有说 SQL Server 版本,所以这对所有人都有效,如果您愿意的话会更好运行多次。

试试这个:

DECLARE @Calls  table (rowID int not null primary key identity(1,1)
                      ,EvId int not null
                      ,CallId varchar(36)
                      ,rowDateTime datetime
                      )
SET NOCOUNT ON
INSERT INTO @Calls VALUES ( 0,'df1cbc93-5cf3-402a-940b-4441f6a7ec5c',' 7/9/2008 8:12:56 PM')
INSERT INTO @Calls VALUES ( 1,'df1cbc93-5cf3-402a-940b-4441f6a7ec5c',' 7/9/2008 8:13:07 PM')
INSERT INTO @Calls VALUES ( 0,'ec1c2078-1765-4377-9126-6f26fe33e4a9','7/10/2008 4:33:10 PM')
INSERT INTO @Calls VALUES (10,'ec1c2078-1765-4377-9126-6f26fe33e4a9','7/10/2008 4:33:13 PM')
INSERT INTO @Calls VALUES ( 1,'ec1c2078-1765-4377-9126-6f26fe33e4a9','7/10/2008 4:33:13 PM')
INSERT INTO @Calls VALUES ( 0,'a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d','7/10/2008 4:33:13 PM')
INSERT INTO @Calls VALUES (10,'a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d','7/10/2008 4:33:15 PM')
INSERT INTO @Calls VALUES ( 1,'a3c3b9a0-a23b-4dda-b4e4-e82f0209c94d','7/10/2008 4:33:15 PM')
INSERT INTO @Calls VALUES ( 0,'d23f393d-0272-445a-8670-3f71b016174e','7/10/2008 4:33:15 PM')
INSERT INTO @Calls VALUES (10,'d23f393d-0272-445a-8670-3f71b016174e','7/10/2008 4:33:17 PM')
INSERT INTO @Calls VALUES ( 1,'d23f393d-0272-445a-8670-3f71b016174e','7/10/2008 4:33:17 PM')
--I added more test data, to hit more cases
INSERT INTO @Calls VALUES ( 0,'111111111111111111111111111111111111','7/10/2008 4:10:00 PM')
INSERT INTO @Calls VALUES (10,'111111111111111111111111111111111111','7/10/2008 4:11:00 PM')
INSERT INTO @Calls VALUES ( 1,'111111111111111111111111111111111111','7/10/2008 4:11:00 PM')
INSERT INTO @Calls VALUES ( 0,'222222222222222222222222222222222222','7/10/2008 4:15:00 PM')
INSERT INTO @Calls VALUES (10,'222222222222222222222222222222222222','7/10/2008 4:16:00 PM')
INSERT INTO @Calls VALUES ( 1,'222222222222222222222222222222222222','7/10/2008 4:16:00 PM')
INSERT INTO @Calls VALUES ( 0,'333333333333333333333333333333333333','7/10/2008 4:09:00 PM')
INSERT INTO @Calls VALUES (10,'333333333333333333333333333333333333','7/10/2008 4:18:00 PM')
INSERT INTO @Calls VALUES ( 1,'333333333333333333333333333333333333','7/10/2008 4:18:00 PM')
INSERT INTO @Calls VALUES ( 0,'444444444444444444444444444444444444','7/10/2008 4:13:00 PM')
INSERT INTO @Calls VALUES (10,'444444444444444444444444444444444444','7/10/2008 4:14:00 PM')
INSERT INTO @Calls VALUES ( 1,'444444444444444444444444444444444444','7/10/2008 4:14:00 PM')
INSERT INTO @Calls VALUES ( 0,'555555555555555555555555555555555555','7/10/2008 4:13:00 PM')
SET NOCOUNT OFF

DECLARE @StartRange  datetime
DECLARE @EndRange    datetime

SET @StartRange='7/10/2008 4:12:00 PM'
SET @EndRange  ='7/10/2008 4:15:00 PM'

SET @EndRange=DATEADD(mi,1,@EndRange)

--this lists the match time and each calls details in progress at that time
SELECT
    DATEADD(mi,n.Number-1,c.StartTime) AS 'TimeOfMatch'
        ,c.CallID
        ,c.StartTime,c.EndTime
    FROM (SELECT --this derived table joins together the start and end dates into a single row, filtering out rows more than 90 minutes before the start range (if calls are longer than 90 minutes, increase this) and filters out any rows after the end date (will consider call done at end date then)
              CallID, MIN(rowDateTime) AS StartTime, CASE  WHEN MAX(rowDateTime)=MIN(rowDateTime) THEN @EndRange ELSE MAX(rowDateTime) END  AS EndTime
              FROM @Calls 
              WHERE rowDateTime>=DATEADD(mi,-90,@StartRange) --AND rowDateTime<=@EndRange
              GROUP BY CallID
         ) c
        INNER JOIN Numbers   n ON DATEDIFF(mi,c.StartTime,c.EndTime)+1>=n.Number
    WHERE DATEADD(mi,n.Number-1,c.StartTime)>=@StartRange AND DATEADD(mi,n.Number-1,c.StartTime)<@EndRange
    ORDER BY 1

--this lists just the match time and the call count
SELECT
    DATEADD(mi,n.Number-1,c.StartTime) AS 'TimeOfMatch'
        ,c.CallID
        ,c.StartTime,c.EndTime
    FROM (SELECT --this derived table joins together the start and end dates into a single row, filtering out rows more than 90 minutes before the start range (if calls are longer than 90 minutes, increase this) and filters out any rows after the end date (will consider call done at end date then)
              CallID, MIN(rowDateTime) AS StartTime, CASE  WHEN MAX(rowDateTime)=MIN(rowDateTime) THEN @EndRange ELSE MAX(rowDateTime) END  AS EndTime
              FROM @Calls 
              WHERE rowDateTime>=DATEADD(mi,-90,@StartRange) --AND rowDateTime<=@EndRange
              GROUP BY CallID
         ) c
        INNER JOIN Numbers   n ON DATEDIFF(mi,c.StartTime,c.EndTime)+1>=n.Number
    WHERE DATEADD(mi,n.Number-1,c.StartTime)>=@StartRange AND DATEADD(mi,n.Number-1,c.StartTime)<@EndRange
    ORDER BY 1

这是输出:

TimeOfMatch             CallID                               StartTime               EndTime
----------------------- ------------------------------------ ----------------------- -----------------------
2008-07-10 16:12:00.000 333333333333333333333333333333333333 2008-07-10 16:09:00.000 2008-07-10 16:18:00.000
2008-07-10 16:13:00.000 333333333333333333333333333333333333 2008-07-10 16:09:00.000 2008-07-10 16:18:00.000
2008-07-10 16:13:00.000 444444444444444444444444444444444444 2008-07-10 16:13:00.000 2008-07-10 16:14:00.000
2008-07-10 16:13:00.000 555555555555555555555555555555555555 2008-07-10 16:13:00.000 2008-07-10 16:16:00.000
2008-07-10 16:14:00.000 555555555555555555555555555555555555 2008-07-10 16:13:00.000 2008-07-10 16:16:00.000
2008-07-10 16:14:00.000 444444444444444444444444444444444444 2008-07-10 16:13:00.000 2008-07-10 16:14:00.000
2008-07-10 16:14:00.000 333333333333333333333333333333333333 2008-07-10 16:09:00.000 2008-07-10 16:18:00.000
2008-07-10 16:15:00.000 333333333333333333333333333333333333 2008-07-10 16:09:00.000 2008-07-10 16:18:00.000
2008-07-10 16:15:00.000 555555555555555555555555555555555555 2008-07-10 16:13:00.000 2008-07-10 16:16:00.000
2008-07-10 16:15:00.000 222222222222222222222222222222222222 2008-07-10 16:15:00.000 2008-07-10 16:16:00.000

(10 row(s) affected)

TimeOfMatch             
----------------------- -----------
2008-07-10 16:12:00.000 1
2008-07-10 16:13:00.000 3
2008-07-10 16:14:00.000 3
2008-07-10 16:15:00.000 3

(4 row(s) affected)

您将需要对 rowDateTime+CallId 的复合索引。但是,为了获得最佳性能,如果您创建了一个包含单个调用的开始日期和结束日期的新表(在 startdate+CallId 上的聚集索引)(可能在 EvId=0 插入开始日期时使用触发器,并且当 EvId =1 更新结束日期)然后可以使用这个新表删除派生表。

于 2009-08-18T13:47:14.887 回答
0

试试这个 :

DECLARE @tblCalls TABLE(ActionEffect int, ActionTime datetime)

INSERT INTO @tblCalls(ActionEffect, ActionTime)
    SELECT 1, [DateTime]
    FROM tblCallRecords
    WHERE EviD = 0

INSERT INTO @tblCalls(ActionEffect, ActionTime)
    SELECT -1, [DateTime]
    FROM tblCallRecords
    WHERE EvID > 0

(我假设 EvID 不是 0 表示通话结束?)

然后,要获取任何给定时刻的呼叫次数,您可以:

SELECT Sum(ActionEffect)
FROM @tblCalls
WHERE ActionTime < @GivenMoment

不过,1500 万条记录不太好。

现在,如果你想要一个运行的总数,你可能需要做这样的事情:

SELECT a.ActionTime, Sum(b.ActionEffect) AS OpenCalls
FROM @tblCalls AS a
LEFT JOIN @tblCalls AS b ON a.ActionTime > b.ActionTime
GROUP BY a.ActionTime

这很快就会变得巨大。我想我会运行一次,将结果存储在一个表中,然后修改我的通话记录机制代码,以便在来电时即时更新它。

于 2009-08-21T19:35:29.023 回答
0

这不是一个解决方案,而只是提出一些想法。尚未对此进行测试,因此如果有垃圾,请随时将它们击落。

这种假设有两件事

1) DateTime 和 UniqueID 上有一个索引

2) 通话不会持续超过一定时间长度(比如 24 小时或 48 小时),或者如果这样做可以忽略。

如果没有,那么您可能会停止阅读。

如果是,如果您从类似的查询开始

 Select CallId, 
     Min(DateTime) as StartOfCall , Max(DateTime) as EndofCall        
 from Call_log
 where
    (evid = 0 or evid=1)
 and DateTime between @ExtendedStartPeriod and @ExtendedEndPeriod

其中 ExtendedStartPeriod 和 ExtendedEndPeriod 是您实际经期的前一天和后一天(如果您的最长通话时间为 48 小时,则为两天)

这会给你一些你不想要的记录,所以你做一个进一步的查询来删除这些

Select UniqueID from (...) table1
where StartOfCall <= @EndDate or EndOfCall >= @StartDate

这应该(我认为)排除在您的完成期之后开始的呼叫或在开始日期之前结束的呼叫。

接下来我们执行另一个外部查询

Select DateTime, 
  CallChange = Case 
  When Evid = 0 then 1
  When Evid = 1 then -1
  else 0
 end
 from call_log 
 where 
  unique_id in ( ... )  
  and (evid = 0 or evid=1)
 and DateTime between @ExtendedStartPeriod and @ExtendedEndPeriod 

这应该为您提供事件时间列表以及它们是增加还是减少呼叫次数。在您的示例中,类似

         7/9/2008 8:12:56 PM  1
         7/9/2008 8:13:07 PM -1
        7/10/2008 4:33:10 PM  1
        7/10/2008 4:33:13 PM -1
        7/10/2008 4:33:13 PM  1
        7/10/2008 4:33:15 PM -1
        7/10/2008 4:33:15 PM  1
        7/10/2008 4:33:17 PM -1

如果每秒调用量非常大,则按分钟分组可能有助于减少从 sql 返回的数据的大小。

甚至可以做进一步的查询

Select 
   Count(CallChange) ,
   DatePart("yyyy", DateTime) , 
   DatePart("mm", DateTime),
   DatePart("dd", DateTime),
   DatePart("hh", DateTime),
   DatePart("mi", DateTime)
   DatePart("ss", DateTime)
From
   ( ...) 

  Group By
     DatePart("yyyy", DateTime) , 
     DatePart("mm", DateTime),
     DatePart("dd", DateTime),
     DatePart("hh", DateTime),
     DatePart("mi", DateTime)
     DatePart("ss", DateTime)

这就是我可以使用 Sql 的程度,也许有人可以更进一步,否则我认为需要做一些 C# 来保持每个时期的事务的运行计数。

于 2009-08-21T21:22:22.783 回答