3

SQL Server データベースにイベント ログがあります。基本的に、電話がかけられた時間とコールセンターで電話が終わった時間 (2 つの異なる記録) と、その他のいくつかの詳細が記録されます。このデータを使用して、特定の時点で使用されている電話回線の数を把握しようとしています。これは理想的ですが (速度があまり犠牲にならない場合)、SQL クエリでこれを判断する良い方法は思いつきません。

私が最初に考えたのは、プログラムで各通話の開始イベントと終了イベントを照会して、通話時間を決定することでした。次に、各時間単位をステップ実行して、任意の時点で進行中の通話数を集計できました。C# などで線形メソッドを使用する代わりに、SQL でこれを行う方法はありますか?

編集: 呼び出しには一意の ID があります。必要に応じて、セッション ID。また、開始イベントと終了イベントは 2 つの異なるレコードであり、単一のレコードではありません。これは私が思うにこれを少し複雑にします。また、このテーブルには 1,500 万を超えるレコードが含まれています。

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.5 GB RAM シングル プロセッサ ラップトップで 1.1 秒 (@temp テーブルの作成に 628 ミリ秒、現在のカウントを含むタイムラインの作成に 505 ミリ秒) で 10 分間隔のイベントを一貫して生成しました。大きなテーブルでのパフォーマンスは、呼び出しの最大継続時間の制限が導入された場合に改善される可能性があります。これは、開始時の既存の呼び出しの検索を下限で制限できるためです (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

私のクエリ例を使用する前に、「ヘルパー」テーブルを設定する必要があります。これを行う必要があるのは、データベースごとに 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 の複合インデックスが必要になります。ただし、最高のパフォーマンスを得るには、単一の通話の開始日と終了日の両方を含む新しいテーブル (開始日 + CallId のクラスター化インデックス) を作成した場合 (おそらく EvId=0 が開始日で挿入されたときにトリガーを使用し、EvId が=1 更新終了日) の場合、派生テーブルはこの新しいテーブルで削除される可能性があります。

于 2009-08-18T13:47:14.887 に答える
0

これは解決策ではありませんが、いくつかのアイデアを投げかけているだけです。これはテストしていないので、ゴミがあれば遠慮なく撃ち落としてください。

この種のことは、2つのことを前提としています

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 は、実際の生理の 1 日前と 1 日後 (最長通話時間が 48 時間の場合は 2 日) です。

これにより、不要なレコードがいくつか得られるため、これらを削除するためにさらにクエリを実行します

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

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でできることと同じくらいですが、誰かがそれをさらに進めることができるかもしれません。

于 2009-08-21T21:22:22.783 に答える
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

(0 以外の EvID は通話の終了を示していると思いますか?)

次に、任意の時点での呼び出し数を取得するには、次のようにします。

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

ただし、1,500 万件のレコードではあまり良くありません。

現在、これの現在の合計が必要な場合は、おそらく次のようなことを行う必要があります。

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 に答える