sql >> Database >  >> RDS >> Sqlserver

Datumbereik snijpunt splitsen in SQL

Het probleem dat u met dit probleem zult hebben, is dat naarmate de dataset groeit, de oplossingen om het op te lossen met TSQL niet goed zullen schalen. Het onderstaande maakt gebruik van een reeks tijdelijke tabellen die ter plekke zijn gemaakt om het probleem op te lossen. Het splitst elk datumbereik in zijn respectieve dagen met behulp van een getallentabel. Dit is waar het niet zal schalen, voornamelijk vanwege je open ranged NULL-waarden die oneindig lijken te zijn, dus je moet een vaste datum ver in de toekomst omwisselen die het conversiebereik beperkt tot een haalbare tijdsduur. U kunt waarschijnlijk betere prestaties behalen door een tabel met dagen of een kalendertabel te maken met de juiste indexering voor een optimale weergave van elke dag.

Nadat de bereiken zijn gesplitst, worden de beschrijvingen samengevoegd met behulp van XML PATH, zodat elke dag in de reeksen alle beschrijvingen ervoor bevat. Met rijnummering op PersonID en Date kunnen de eerste en laatste rij van elk bereik worden gevonden met behulp van twee NOT EXISTS-controles om gevallen te vinden waar een vorige rij niet bestaat voor een overeenkomende PersonID en Description-set, of waar de volgende rij niet bestaat t bestaat voor een overeenkomende PersonID en Beschrijving set.

Deze resultatenset wordt vervolgens opnieuw genummerd met ROW_NUMBER, zodat ze kunnen worden gekoppeld om de uiteindelijke resultaten op te bouwen.

/*
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

*/

De bovenstaande oplossing zal ook hiaten tussen aanvullende beschrijvingen opvangen, dus als u een andere beschrijving voor PersonID 18 zou toevoegen, laat u een hiaat achter:

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

Het zal de leemte op passende wijze opvullen. Zoals aangegeven in de opmerkingen, zou u geen naaminformatie in deze tabel moeten hebben, deze moet worden genormaliseerd naar een personentabel waaraan in het eindresultaat kan worden JOIN'd. Ik heb deze andere tabel gesimuleerd door een SELECT DISTINCT te gebruiken om een ​​tijdelijke tabel te bouwen om die JOIN te maken.



  1. Hoe maak je een unieke index op een NULL-kolom?

  2. beste manier om een ​​datumreeks te converteren en te valideren

  3. Waarom vereist Oracle 12c query dubbele aanhalingstekens rond de tafel?

  4. Krijgt u het aantal rijen ingevoegd voor ON DUPLICATE KEY UPDATE meerdere inserts?