2009-08-17 7 views
3

J'ai un journal des événements dans une base de données SQL Server. Essentiellement, il enregistre quand un appel a été fait et quand cet appel s'est terminé à un centre d'appels (comme deux enregistrements différents), ainsi que quelques autres détails. J'essaie d'avoir une idée du nombre de lignes téléphoniques utilisées à un moment donné avec ces données. Je ne peux pas penser à un bon moyen d'avoir une requête SQL pour déterminer cela pour moi, bien que ce serait idéal (si elle ne sacrifie pas beaucoup de vitesse). J'ai d'abord pensé qu'un programme interrogeait les événements de début et de fin pour chaque appel, en déterminant la durée de l'appel. Je pourrais alors parcourir chaque unité de temps, en gardant un compte du nombre d'appels en cours à un moment donné. Est-ce que je peux faire cela en SQL au lieu d'utiliser une méthode linéaire en C# ou quelque chose de similaire?Analyse du journal des événements en fonction du temps, à partir de SQL

Modifier: Il existe un ID unique pour les appels. Un identifiant de session, si vous voulez. En outre, les événements de début et de fin sont deux enregistrements différents - pas un seul enregistrement. Cela complique un peu ce que je pense. En outre, cette table contient plus de 15 millions d'enregistrements.

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 
+0

Existe-t-il un identifiant unique pour chacun des appels? –

+0

Jetez un oeil à.http: //stackoverflow.com/questions/781895/checking-for-time-range-overlap-the-watchman-problem-sql cela montre comment calculer les chevauchements – pjp

+0

il n'y a pas d'appels de "chevauchement", certains ont les mêmes points de début/fin. de toute façon, voir ma dernière édition pour l'exemple de code ... –

Répondre

1

Avant d'utiliser mes exemples de requêtes, vous avez besoin de mettre en place une table « d'aide », il vous suffit de le faire une fois par base de données:

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 @[email protected]+1 
    INSERT INTO Numbers VALUES (@x) 
END 

Cela crée essentiellement une table contenant une Une seule colonne contenant des valeurs de 1 à 8000. Vous pouvez utiliser un CTE pour faire la même chose, mais puisque vous ne dites pas la version de SQL Server, cela fonctionnera pour tous, et c'est mieux si vous allez l'exécuter plusieurs fois.

essayez ceci:

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<[email protected] 
       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)>[email protected] 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<[email protected] 
       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)>[email protected] AND DATEADD(mi,n.Number-1,c.StartTime)<@EndRange 
    ORDER BY 1 

est ici la sortie:

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) 

Vous aurez besoin d'un indice composite sur rowDateTime + CALLID. Cependant, pour les meilleures performances, si vous avez créé une nouvelle table (index clusterisé sur startdate + CallId) contenant à la fois les dates de début et de fin d'un appel unique (éventuellement avec un déclencheur lorsque EvId = 0 insérer avec date de début et EvId = 1 date de fin de la mise à jour) alors la table dérivée pourrait être supprimée avec cette nouvelle table.

+0

est ce que vous avez besoin, ou êtes-vous après quelque chose d'autre? si oui faites le moi savoir ... –

+0

Je pense que le seul problème est que j'essaye de représenter graphiquement ces comptes (appels/temps) ainsi j'ai besoin de faire une sorte d'itération pour obtenir le compte pour chaque seconde. Je voudrais spécifier la plage de temps à laquelle il devrait être représenté (c'est-à-dire tous les appels entre 12:00 et 1:00 un certain jour) –

+0

Le seul autre problème est que cette table a 15millions d'enregistrements donc toutes les optimisations seraient une amélioration –

1

Voici une requête qui produit le journal de tous les événements d'une période donnée, ainsi que le nombre actuel d'appels lorsque l'événement s'est produit. Il utilise plusieurs CTE pour constituer les données nécessaires selon une séquence logique d'étapes: sélectionner les appels commencés avant l'heure de début, soustraire les appels terminés avant l'heure de début, ajouter des événements d'appel entre les heures de début et de fin. Cet ensemble de résultats est ensuite utilisé pour produire la chronologie des événements, ainsi que le nombre d'appels en cours à n'importe quel événement. Les CTE sont utilisés simplement parce que je les trouve beaucoup plus faciles à lire et à comprendre que les tables dérivées. Cette requête produit un plan qui n'analyse pas la totalité de la table de consignation, étant donné que les index appropriés existent en place. Il donne des résultats corrects pour chaque intervalle, en tenant compte des appels existants à l'heure de début de l'intervalle. Dans mes tests sur des enregistrements de journaux de 1 mil, il produisait régulièrement des événements à intervalles de 10 minutes en 1.1s (628ms pour produire la table @temp, 505ms pour produire la chronologie avec le nombre actuel) sur un ordinateur portable de 1.5GB RAM. Les performances sur les grandes tables peuvent être améliorées si une restriction sur la durée maximale d'un appel est introduite, car la recherche d'appels existants à l'heure de début peut être bornée à l'extrémité inférieure (DatTime> = heure de début - durée maximale d'un appel) .

L'utilisation d'une variable de table @temp intermédiaire n'est pas élégante, mais est efficace.

Voici un exemple de sortie:

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 

Voici comment j'ai créé et chargé les données de test. Notez l'index clusterisé et l'index non cluster sur la table, ils sont tous les deux critiques.

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 
+0

J'aime les solutions ici. Maintenant, ma question est réduite à: est-ce une meilleure solution que simplement saisir les données et les lancer en C# et effectuer une analyse linéaire? –

+1

Habituellement, oui. Si les données ont une taille significative, le coût de «l'attraper et de le lancer» en C# sera écrasant. –

+0

Je dois ajouter que chaque enregistrement n'a pas d'heure de début et de fin. L'événement de début est un enregistrement unique et l'événement de fin est un autre enregistrement. –

0

Essayez ceci:

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 

(je suppose que les autres de Evid que 0 indique la fin d'un appel?)

Ensuite, pour obtenir le nombre d'appels à un moment donné, vous faire:

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

Pas trop agréable avec 15 millions d'enregistrements, cependant.

Maintenant, si vous vouliez un total en cours d'exécution de cela, vous allez probablement avoir besoin de faire quelque chose comme ceci:

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 

qui obtient rapidement énorme. Je pense que je l'exécuterais une fois, stockerais les résultats dans un tableau et modifierais mon code de mécanisme d'enregistrement d'appel pour le mettre à jour à la volée.

0

Ceci n'est pas une solution, mais juste jeter des idées Là. N'a pas testé cela, alors n'hésitez pas à les abattre s'il y a des ordures.

Cela suppose sorte de deux choses

1) Il y a un index sur DateTime et UniqueID

2) qu'un appel ne va pas durer plus d'une certaine durée (par exemple 24 heures ou 48 heures) ou peut être ignoré s'il le fait.

Sinon, vous pouvez arrêter de lire.

Si oui, si vous commencez avec un quelque chose de requête comme

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 

où ExtendedStartPeriod et ExtendedEndPeriod sont un jour avant et un jour après votre période réelle (ou deux jours si votre longueur d'appel maximale est de 48 heures)

Cela vous donnera quelques enregistrements que vous ne voulez pas, vous faites une requête plus supprimer ces

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

Cela devrait (je pense) exclure e e les appels qui commencent après votre période d'arrivée ou les appels qui se terminent avant la date de début.

Ensuite, nous effectuer une autre requête externe

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 

Cela devrait vous donner une liste des temps d'événements et si elles augmentent ou diminuent le nombre d'appels. Dans votre exemple, quelque chose comme

  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 

S'il y a un volume très élevé d'appels par seconde, il peut aider à regrouper ce par minute pour réduire la taille des données renvoyées de sql.

Il pourrait même par possible de faire une autre requête

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) 

C'est à peu près aussi que je peux aller avec Sql, peut-être quelqu'un peut le prendre plus loin, sinon je pense aurait besoin de faire un peu de C# pour garder un compte courant des transactions par période.

Questions connexes