Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Fractionnement d'intersection de plage de dates dans SQL

Le problème que vous allez avoir avec ce problème est qu'à mesure que l'ensemble de données grandit, les solutions pour le résoudre avec TSQL ne s'adapteront pas bien. Ce qui suit utilise une série de tables temporaires construites à la volée pour résoudre le problème. Il divise chaque entrée de plage de dates en ses jours respectifs à l'aide d'un tableau de nombres. C'est là qu'il ne sera pas mis à l'échelle, principalement en raison de vos valeurs NULL à plage ouverte qui semblent être infinies, vous devez donc échanger une date fixe loin dans le futur qui limite la plage de conversion à une durée réalisable. Vous pourriez probablement obtenir de meilleures performances en créant une table des jours ou une table de calendrier avec une indexation appropriée pour un rendu optimisé de chaque jour.

Une fois les plages divisées, les descriptions sont fusionnées à l'aide de XML PATH afin que chaque jour de la série de plages ait toutes les descriptions répertoriées. La numérotation des lignes par PersonID et Date permet de trouver la première et la dernière ligne de chaque plage à l'aide de deux vérifications NOT EXISTS pour trouver les cas où une ligne précédente n'existe pas pour un PersonID et un ensemble de description correspondants, ou où la ligne suivante n'existe pas. t exister pour un ensemble PersonID et Description correspondant.

Cet ensemble de résultats est ensuite renuméroté à l'aide de ROW_NUMBER afin qu'ils puissent être associés pour créer les résultats finaux.

/*
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

*/

La solution ci-dessus gérera également les écarts entre les descriptions supplémentaires, donc si vous deviez ajouter une autre description pour PersonID 18 en laissant un espace :

INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')

Il comblera le vide de manière appropriée. Comme indiqué dans les commentaires, vous ne devriez pas avoir d'informations sur le nom dans ce tableau, il doit être normalisé dans un tableau de personnes auquel vous pouvez vous JOINDRE dans le résultat final. J'ai simulé cette autre table en utilisant un SELECT DISTINCT pour construire une table temporaire pour créer cette JOIN.