Проблема, с которой вы столкнулись с этой проблемой, заключается в том, что по мере роста набора данных решения по ее решению с TSQL не будут хорошо масштабироваться. Ниже приведена серия временных таблиц, созданных на лету для решения проблемы. Он разбивает каждый элемент диапазона дат в соответствующие дни, используя таблицу чисел. В этом случае он не будет масштабироваться, в основном из-за ваших значений NULL с открытым диапазоном, которые кажутся нестабильными, поэтому вам нужно поменять местами в фиксированную дату далеко в будущее, что ограничивает диапазон преобразования до допустимого периода времени. Вероятно, вы можете увидеть лучшую производительность, построив таблицу дней или таблицу календаря с соответствующей индексацией для оптимизированного рендеринга каждого дня.
После того, как диапазоны разделены, описания объединяются с использованием XML PATH, так что каждый день в серии диапазонов содержит все описания, перечисленные для него. Нумерация строк с помощью PersonID и Date позволяет найти первую и последнюю строку каждого диапазона с помощью двух проверок NOT EXISTS, чтобы найти экземпляры, в которых предыдущая строка не существует для соответствующего набора PersonID и описания, или где следующая строка не имеет значения, t существует для сопоставления идентификатора PersonID и описания.
Этот результирующий набор затем нумеруется с помощью 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')
Он будет заполнить этот пробел соответствующим образом. Как указано в комментариях, вы не должны иметь информацию о названии в этой таблице, она должна быть нормализована в таблице лиц, которая может быть включена в окончательный результат. Я смоделировал эту другую таблицу, используя SELECT DISTINCT, чтобы создать временную таблицу для создания этого JOIN.
Как ваш дизайн обрабатывает несколько членов с тем же именем/фамилией? Не исключено, что данные образца, которые вы предоставили, относятся к трем различным людям, которые называются Джон Смит. –
Это действительная точка, из которой я отредактировал свой вопрос, чтобы отразить эту возможность. Я действительно храню идентификатор для каждого человека, однако в то время, когда я писал вопрос, я не думал о дублирующих именах. Приветствия для обратной связи. – user168369
Есть личный идентификатор - я бы полностью проигнорировал бит названия до окончательного вывода. Выберите – MartW