Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Intersezione dell'intervallo di date Divisione in SQL

Il problema che avrai con questo problema è che man mano che il set di dati cresce, le soluzioni per risolverlo con TSQL non si adattano bene. Di seguito viene utilizzata una serie di tabelle temporanee costruite al volo per risolvere il problema. Suddivide ogni voce dell'intervallo di date nei rispettivi giorni utilizzando una tabella numerica. È qui che non si ridimensiona, principalmente a causa dei valori NULL con intervallo aperto che sembrano essere infiniti, quindi è necessario scambiare in una data fissa molto lontana nel futuro che limiti l'intervallo di conversione a un periodo di tempo fattibile. Probabilmente potresti ottenere prestazioni migliori creando una tabella dei giorni o una tabella del calendario con un'indicizzazione appropriata per il rendering ottimizzato di ogni giorno.

Una volta che gli intervalli sono stati divisi, le descrizioni vengono unite utilizzando XML PATH in modo che ogni giorno nella serie di intervalli abbia tutte le descrizioni elencate per esso. La numerazione delle righe per IDpersona e data consente di trovare la prima e l'ultima riga di ciascun intervallo utilizzando due controlli NOT EXISTS per trovare le istanze in cui non esiste una riga precedente per un set di ID Persona e Descrizione corrispondenti o in cui la riga successiva non esiste t esiste per un ID Persona e un insieme di Descrizione corrispondenti.

Questo set di risultati viene quindi rinumerato utilizzando ROW_NUMBER in modo che possano essere abbinati per creare i risultati finali.

/*
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 soluzione di cui sopra gestirà anche gli spazi vuoti tra le descrizioni aggiuntive, quindi se dovessi aggiungere un'altra descrizione per PersonID 18 lasciando uno spazio vuoto:

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

Colmerà il divario in modo appropriato. Come sottolineato nei commenti, non dovresti avere informazioni sul nome in questa tabella, dovrebbe essere normalizzata in una tabella delle persone a cui può essere unito nel risultato finale. Ho simulato quest'altra tabella utilizzando SELECT DISTINCT per creare una tabella temporanea per creare quel JOIN.