Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Разделяне на пресичане на диапазон от дати в SQL

Проблемът, който ще имате с този проблем, е, че с нарастването на набора от данни решенията за решаването му с TSQL няма да се мащабират добре. По-долу използва серия от временни таблици, изградени в движение, за да разреши проблема. Той разделя всеки запис в диапазона от дати на съответните му дни с помощта на таблица с числа. Това е мястото, където няма да се мащабира, главно поради вашите NULL стойности с отворен диапазон, които изглеждат безкрайни, така че трябва да замените фиксирана дата далеч в бъдещето, което ограничава диапазона на преобразуване до възможен период от време. Вероятно бихте могли да видите по-добра производителност, като създадете таблица с дни или таблица с календар с подходящо индексиране за оптимизирано изобразяване на всеки ден.

След като диапазоните са разделени, описанията се обединяват с помощта на XML PATH, така че всеки ден в серията от диапазони да има всички описания, посочени за него. Номерирането на редове по PersonID и дата позволява първият и последният ред от всеки диапазон да бъдат намерени с помощта на две проверки НЕ СЪЩЕСТВУВА, за да се намерят случаи, когато предишен ред не съществува за съвпадащ 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sql сървър - Избягвайте отложената компилация

  2. Linq:Сортиране по дата, когато се съхранява като текст

  3. Получаване на минимум две стойности в SQL

  4. Как работи функцията STR() в SQL Server (T-SQL)

  5. TSQL - Съединение с използване на пълен текст СЪДЪРЖА