Le problème que vous allez rencontrer avec ce problème est qu'à mesure que l'ensemble de données se développe, les solutions pour le résoudre avec TSQL ne seront pas bien adaptées. Le tableau ci-dessous 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 dans ses jours respectifs à l'aide d'une table de nombres. C'est là qu'il ne sera pas mis à l'échelle, principalement en raison de vos valeurs NULL à intervalle ouvert qui semblent être inifinity, de sorte que vous devez permuter une date fixe loin dans le futur qui limite la plage de conversion à une durée possible. Vous pourriez probablement voir de meilleures performances en construisant une table de 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, de sorte que chaque jour de la série comporte toutes les descriptions répertoriées. Numérotation des lignes par PersonID et Date permet de trouver la première et dernière rangée de chaque plage en utilisant deux vérifications NOT EXISTS pour trouver des instances où une ligne précédente n'existe pas pour un ensemble PersonID et Description correspondant, ou où la ligne suivante doesn ' t existe pour un ID de personne et un ensemble de descriptions correspondants.
Cet ensemble de résultats est ensuite renuméroté à l'aide de ROW_NUMBER afin qu'ils puissent être associés pour générer 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 traitera également des é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 va combler le vide de manière appropriée. Comme indiqué dans les commentaires, vous ne devriez pas avoir d'informations de nom dans ce tableau, il devrait être normalisé à une table de personnes qui peut être joint à dans le résultat final. J'ai simulé cette autre table en utilisant un SELECT DISTINCT pour créer une table temporaire pour créer ce JOIN.
Comment votre conception gère-t-elle plusieurs membres avec le même prénom/nom? Ce n'est pas au-delà des limites de la possibilité que les exemples de données que vous avez fournis se rapportent à trois personnes différentes appelées John Smith. –
C'est un point valide, dont j'ai édité ma question pour refléter cette possibilité. Je suis en effet en train de stocker un identifiant pour chaque personne, mais au moment où j'ai écrit la question je ne pensais pas à des noms en double. Bravo pour les commentaires. – user168369
Il y a un PersonID - j'ignorerais complètement le nom jusqu'à la sortie finale. Sélectionnez – MartW