O código de trabalho a seguir tem como alvo o SQL-Server 2012 e eu destilei o problema real em um cenário administrável com o exemplo de código mostrado abaixo também apresentado em SQL Fiddle.
Busco feedback sobre as melhores práticas de uso de CTEs e tabelas temporárias e gostaria de saber se o seguinte poderia ser simplificado como tal.
( Observe que isso também foi solicitado na revisão de código ( processo de relatório de inserção de registros de lacunas ) sem respostas até o momento.)
Premissa
Existem duas Tabelas que abrigam eventos com registro de data e hora para Agentes e Gerenciamento. Um relatório será executado na tabela de agentes. Mas dentro da tabela Agente, se houver intervalo(s) de eventos de mais de 5 horas, o relatório resultante deverá ser preenchido a partir da tabela de eventos de gerenciamento para esse intervalo.
Agente
CREATE TABLE AgentInteractions
(
[Event] VARCHAR(12) NOT NULL,
[Timestamp] [DateTime] NOT NULL
);
INSERT INTO dbo.AgentInteractions( Event, TimeStamp )
VALUES ( 'Alpha', '24-Jan-2018 3:04:00 PM' ),
( 'Beta', '24-Jan-2018 10:04:00 PM' ), -- Gap 7 hours
( 'Omega', '25-Jan-2018 2:04:00 AM' ); -- No Gap
Gestão
CREATE TABLE ManagementInteractions
(
[Event] VARCHAR(12) NOT NULL,
[Timestamp] [DateTime] NOT NULL
);
INSERT INTO dbo.ManagementInteractions( Event, TimeStamp )
VALUES ( '5pm', '24-Jan-2018 5:00:00 PM' ), -- Gap Filler #1
( '8pm', '24-Jan-2018 8:00:00 PM' ), -- Gap Filler #2
( 'Midnight', '25-Jan-2018 12:00:00 AM' ); -- Not used
Relatório inicial (ou primeira etapa)
Com o seguinte sql que coloca o relatório na tabela temporária, ele calcula a diferença de tempo entre as linhas e configura uma sequência que não estava presente nos dados de origem.
CTE para tabela temporária
IF OBJECT_ID('tempdb..#Actions') IS NOT NULL DROP TABLE #Actions;
WITH AgentActions AS
( SELECT ROW_NUMBER() OVER ( ORDER BY [Timestamp] ) AS [Sequence], -- Create an index number ordered by time.
Event ,
Timestamp
FROM AgentInteractions
)
SELECT CAST('Agent' AS VARCHAR(20)) AS [Origin] ,
AgentActions.Sequence ,
AgentActions.Event ,
( SELECT Other.Timestamp
FROM AgentActions Other
WHERE Other.Sequence = AgentActions.Sequence - 1
) AS Previous ,
AgentActions.Timestamp ,
ISNULL(DATEDIFF(HOUR,
( SELECT Other.Timestamp
FROM AgentActions Other
WHERE Other.Sequence = AgentActions.Sequence - 1),
AgentActions.Timestamp),
0) AS TimeFromLastPoint
INTO #Actions
FROM AgentActions;
Resultado de um select into #Actions
, observe o intervalo de 7 horas:
Determinar lacunas
O sql a seguir determina as lacunas e insere os registros da tabela de gerenciamento que preenchem a lacuna.
Inserir eventos de gerenciamento nas lacunas
WITH Gaps AS
( SELECT AC.Origin ,
AC.Sequence ,
AC.Event ,
AC.Previous ,
AC.Timestamp ,
AC.TimeFromLastPoint
FROM #Actions AC
WHERE AC.TimeFromLastPoint > 5
)
INSERT INTO #Actions ( [Origin] , [Event] , [Timestamp] , TimeFromLastPoint)
SELECT 'Management' ,
[Event] ,
[Timestamp] ,
0 -- We will figure this out after the insert.
FROM ManagementInteractions MAN
WHERE EXISTS ( SELECT *
FROM Gaps
WHERE MAN.Timestamp BETWEEN Gaps.Previous
AND Gaps.Timestamp );
SELECT Origin , Sequence , Event , Previous , Timestamp , TimeFromLastPoint
FROM #Actions ORDER BY Timestamp;
Resultado da seleção.
Relatório final
O relatório final faz o que foi feito na etapa 1, ele resequencia e determina as durações de tempo entre todos os pontos.
CTE Deja-vu Relatório Final
WITH Combined AS
(
SELECT Origin,
ROW_NUMBER() OVER ( ORDER BY [Timestamp] ) AS [Sequence], -- Create an index number ordered by time.
Event ,
Timestamp
FROM #Actions
)
SELECT Combined.Origin,
Combined.Sequence ,
Combined.Event ,
( SELECT Other.Timestamp
FROM Combined Other
WHERE Other.Sequence = Combined.Sequence - 1
) AS Previous ,
Combined.Timestamp ,
ISNULL(DATEDIFF(HOUR,
( SELECT Other.Timestamp
FROM Combined Other
WHERE Other.Sequence = Combined.Sequence
- 1
), Combined.Timestamp), 0) AS TimeFromLastPoint
FROM Combined;
Aqui estão os resultados
Resumo
Alguma das etapas pode ser combinada ou há alguma prática que devo usar ou evitar com o código sql acima?
Eu acho que a abordagem que você criou é muito boa. Aqui está uma simplificação proposta que evita a junção automática
dbo.AgentInteractions
usando a nova função LAG do SQL Server 2012 , bem como CTEs para destilar a lógica em uma única consulta (também no SQL Fiddleformulário).Olhando para o plano de consulta para esta versão de consulta única, ele faz exatamente o que você esperaria: classificar as ações do agente por registro de data e hora, preencher as lacunas acessando as ações de gerenciamento e, em seguida, classificar esse conjunto combinado de ações resultantes para fazer o sequenciamento final: