sql >> Base de Datos >  >> RDS >> Sqlserver

División de intersección de rango de fechas en SQL

El problema que tendrá con este problema es que a medida que crece el conjunto de datos, las soluciones para resolverlo con TSQL no escalarán bien. A continuación se utiliza una serie de tablas temporales construidas sobre la marcha para resolver el problema. Divide cada entrada de rango de fechas en sus respectivos días usando una tabla de números. Aquí es donde no se escalará, principalmente debido a sus valores NULL de rango abierto que parecen ser infinitos, por lo que debe cambiar una fecha fija en el futuro que limita el rango de conversión a un período de tiempo factible. Es probable que obtenga un mejor rendimiento al crear una tabla de días o una tabla de calendario con la indexación adecuada para optimizar la representación de cada día.

Una vez que se dividen los rangos, las descripciones se fusionan mediante XML PATH para que cada día de la serie de rango tenga todas las descripciones enumeradas. La numeración de filas por ID de persona y fecha permite encontrar la primera y la última fila de cada rango utilizando dos comprobaciones de NO EXISTE para encontrar instancias en las que no existe una fila anterior para un conjunto de ID de persona y descripción coincidentes, o donde la siguiente fila no existe. t existe para un conjunto de ID de persona y descripción coincidentes.

Este conjunto de resultados luego se vuelve a numerar usando ROW_NUMBER para que puedan emparejarse para construir los resultados finales.

/*
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 solución anterior también manejará los espacios entre descripciones adicionales, por lo que si agregara otra descripción para PersonID 18 dejando un espacio:

INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')

Llenará el vacío apropiadamente. Como se señaló en los comentarios, no debe tener información de nombre en esta tabla, debe normalizarse a una tabla de personas a la que se puede JOIN en el resultado final. Simulé esta otra tabla usando SELECT DISTINCT para construir una tabla temporal para crear ese JOIN.