Problemet du kommer att ha med det här problemet är att när datamängden växer kommer lösningarna för att lösa det med TSQL inte skalas bra. Nedanstående använder en serie tillfälliga tabeller byggda i farten för att lösa problemet. Den delar upp varje datumintervall i sina respektive dagar med hjälp av en siffertabell. Det är här det inte kommer att skalas, främst på grund av dina öppna NULL-värden som verkar vara oändliga, så du måste byta in ett fast datum långt in i framtiden som begränsar konverteringsintervallet till en möjlig tidsperiod. Du kan sannolikt se bättre prestanda genom att skapa en tabell över dagar eller en kalendertabell med lämplig indexering för optimerad rendering av varje dag.
När intervallen är uppdelade slås beskrivningarna samman med XML PATH så att varje dag i intervallserien har alla beskrivningar för sig. Radnumrering efter person-ID och datum gör att den första och sista raden i varje intervall kan hittas med hjälp av två INTE FINNS-kontroller för att hitta instanser där en tidigare rad inte finns för en matchande person-ID och beskrivningsuppsättning, eller där nästa rad inte finns. t existerar för en matchande person-ID och beskrivningsuppsättning.
Denna resultatuppsättning numreras sedan om med ROW_NUMBER så att de kan paras ihop för att bygga de slutliga resultaten.
/*
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
*/
Ovanstående lösning kommer också att hantera luckor mellan ytterligare beskrivningar också, så om du skulle lägga till ytterligare en beskrivning för PersonID 18 och lämnar ett gap:
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')
Det kommer att fylla luckan på lämpligt sätt. Som påpekats i kommentarerna bör du inte ha namninformation i den här tabellen, den bör normaliseras till en persontabell som kan anslutas till i slutresultatet. Jag simulerade den här andra tabellen genom att använda en SELECT DISTINCT för att bygga en tillfällig tabell för att skapa den JOIN.