Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Divisão de interseção de intervalo de datas em SQL


O problema que você terá com esse problema é que, à medida que o conjunto de dados cresce, as soluções para resolvê-lo com TSQL não serão bem dimensionadas. O abaixo usa uma série de tabelas temporárias criadas em tempo real para resolver o problema. Ele divide cada entrada de intervalo de datas em seus respectivos dias usando uma tabela de números. É aqui que ele não será dimensionado, principalmente devido aos valores NULL de intervalo aberto que parecem ser infinitos, portanto, você precisa trocar em uma data fixa no futuro que limita o intervalo de conversão a um período de tempo viável. Você provavelmente poderá ver um desempenho melhor criando uma tabela de dias ou uma tabela de calendário com indexação apropriada para renderização otimizada de cada dia.

Depois que os intervalos são divididos, as descrições são mescladas usando XML PATH para que cada dia na série de intervalos tenha todas as descrições listadas para ele. A numeração de linhas por PersonID e Data permite que a primeira e a última linha de cada intervalo sejam encontradas usando duas verificações NOT EXISTS para localizar instâncias em que uma linha anterior não existe para um conjunto de PersonID e Descrição correspondentes, ou onde a próxima linha não existe t existe para um conjunto PersonID e Description correspondentes.

Este conjunto de resultados é então renumerado usando ROW_NUMBER para que possam ser emparelhados para criar os resultados finais.
/*
SET DATEFORMAT dmy
USE tempdb;
GO
CREATE TABLE Schedule
( PersonID int, 
 Surname nvarchar(30), 
 FirstName nvarchar(30), 
 Description nvarchar(100), 
 StartDate datetime, 
 EndDate datetime)
GO
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Poker Club', '01/01/2009', NULL)
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Library', '05/01/2009', '18/01/2009')
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/01/2009', '28/01/2009')
INSERT INTO Schedule VALUES (26, 'Adams', 'Jane', 'Pilates', '03/01/2009', '16/02/2009')
GO

*/

SELECT 
 PersonID, 
 Description, 
 theDate
INTO #SplitRanges
FROM Schedule, (SELECT DATEADD(dd, number, '01/01/2008') AS theDate
    FROM master..spt_values
    WHERE type = N'P') AS DayTab
WHERE theDate >= StartDate 
  AND theDate <= isnull(EndDate, '31/12/2012')

SELECT 
 ROW_NUMBER() OVER (ORDER BY PersonID, theDate) AS rowid,
 PersonID, 
 theDate, 
 STUFF((
  SELECT '/' + Description
  FROM #SplitRanges AS s
  WHERE s.PersonID = sr.PersonID 
    AND s.theDate = sr.theDate
  FOR XML PATH('')
  ), 1, 1,'') AS Descriptions
INTO #MergedDescriptions
FROM #SplitRanges AS sr
GROUP BY PersonID, theDate


SELECT 
 ROW_NUMBER() OVER (ORDER BY PersonID, theDate) AS ID, 
 *
INTO #InterimResults
FROM
(
 SELECT * 
 FROM #MergedDescriptions AS t1
 WHERE NOT EXISTS 
  (SELECT 1 
   FROM #MergedDescriptions AS t2 
   WHERE t1.PersonID = t2.PersonID 
     AND t1.RowID - 1 = t2.RowID 
     AND t1.Descriptions = t2.Descriptions)
UNION ALL
 SELECT * 
 FROM #MergedDescriptions AS t1
 WHERE NOT EXISTS 
  (SELECT 1 
   FROM #MergedDescriptions AS t2 
   WHERE t1.PersonID = t2.PersonID 
     AND t1.RowID = t2.RowID - 1
     AND t1.Descriptions = t2.Descriptions)
) AS t

SELECT DISTINCT 
 PersonID, 
 Surname, 
 FirstName
INTO #DistinctPerson
FROM Schedule

SELECT 
 t1.PersonID, 
 dp.Surname, 
 dp.FirstName, 
 t1.Descriptions, 
 t1.theDate AS StartDate, 
 CASE 
  WHEN t2.theDate = '31/12/2012' THEN NULL 
  ELSE t2.theDate 
 END AS EndDate
FROM #DistinctPerson AS dp
JOIN #InterimResults AS t1 
 ON t1.PersonID = dp.PersonID
JOIN #InterimResults AS t2 
 ON t2.PersonID = t1.PersonID 
  AND t1.ID + 1 = t2.ID 
  AND t1.Descriptions = t2.Descriptions

DROP TABLE #SplitRanges
DROP TABLE #MergedDescriptions
DROP TABLE #DistinctPerson
DROP TABLE #InterimResults

/*

DROP TABLE Schedule

*/

A solução acima também lidará com lacunas entre descrições adicionais, portanto, se você adicionar outra descrição para PersonID 18, deixando uma lacuna:
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')

Ele preencherá a lacuna adequadamente. Como apontado nos comentários, você não deve ter informações de nome nesta tabela, ela deve ser normalizada para uma Tabela de Pessoas que pode ser JOIN'd no resultado final. Simulei essa outra tabela usando um SELECT DISTINCT para construir uma tabela temporária para criar esse JOIN.