この問題で発生する問題は、データ セットが大きくなるにつれて、TSQL で解決するソリューションが適切に拡張されなくなることです。以下では、オンザフライで構築された一連の一時テーブルを使用して問題を解決しています。数値テーブルを使用して、各日付範囲エントリをそれぞれの日に分割します。これは、主に無限に見えるオープン範囲の NULL 値が原因でスケーリングされない場所であるため、変換の範囲を実現可能な時間に制限する、はるかに将来の固定日付に切り替える必要があります。各日の最適化されたレンダリングのために、適切なインデックスを使用して日付のテーブルまたはカレンダー テーブルを作成することで、パフォーマンスが向上する可能性があります。
範囲が分割されると、XML PATH を使用して説明がマージされるため、範囲シリーズの各日は、すべての説明が一覧表示されます。 PersonID と Date による行の番号付けにより、2 つの NOT EXISTS チェックを使用して各範囲の最初と最後の行を見つけ、一致する PersonID と Description セットの前の行が存在しない場合、または次の行が存在しない場合を見つけることができます。一致する PersonID と Description セットが存在しません。
この結果セットは、ROW_NUMBER を使用して再番号付けされ、最終結果を構築するためにペアにすることができます。
/*
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
*/
上記のソリューションは、追加の説明間のギャップも処理するため、ギャップを残して PersonID 18 の別の説明を追加する場合:
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')
適度に隙間を埋めてくれます。コメントで指摘されているように、このテーブルに名前情報を含めるべきではありません。最終結果で結合できる Persons テーブルに正規化する必要があります。 SELECT DISTINCT を使用して一時テーブルを作成し、その JOIN を作成することで、この別のテーブルをシミュレートしました。