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

Datointerval skæringsopdeling i SQL

Problemet du vil have med dette problem er, at når datasættet vokser, vil løsningerne til at løse det med TSQL ikke skaleres godt. Nedenstående bruger en række midlertidige tabeller, der er bygget i gang for at løse problemet. Den opdeler hver datointervalindtastning i sine respektive dage ved hjælp af en taltabel. Det er her, det ikke skaleres, primært på grund af dine åbne NULL-værdier, som ser ud til at være uendelige, så du er nødt til at bytte om på en fast dato langt ud i fremtiden, der begrænser konverteringsområdet til et muligt tidsrum. Du kan sandsynligvis opnå en bedre ydeevne ved at bygge en tabel over dage eller en kalendertabel med passende indeksering til optimeret gengivelse af hver dag.

Når områderne er opdelt, flettes beskrivelserne ved hjælp af XML PATH, så hver dag i rækkeserien har alle beskrivelserne anført for sig. Rækkenummerering efter person-id og dato gør det muligt at finde den første og sidste række i hvert område ved at bruge to IKKE EKSISTERS-kontroller for at finde tilfælde, hvor en tidligere række ikke eksisterer for et matchende person-id og beskrivelsessæt, eller hvor den næste række ikke eksisterer. t eksisterer for et matchende person-id og beskrivelsessæt.

Dette resultatsæt omnummereres derefter ved hjælp af ROW_NUMBER, så de kan parres for at opbygge de endelige resultater.

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

*/

Ovenstående løsning vil også håndtere huller mellem yderligere beskrivelser, så hvis du skulle tilføje en anden beskrivelse til PersonID 18, hvilket efterlader et hul:

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

Det vil fylde hullet på passende vis. Som påpeget i kommentarerne, skal du ikke have navneoplysninger i denne tabel, den skal normaliseres til en persontabel, der kan tilsluttes i det endelige resultat. Jeg simulerede denne anden tabel ved at bruge en SELECT DISTINCT til at bygge en midlertidig tabel for at skabe den JOIN.



  1. hvordan man ignorerer en betingelse i where-klausulen

  2. Postgres HStore-fejl - Ukendt operatør

  3. MySQL fjerner hurtigt dubletter fra stor database

  4. Sådan administrerer du dine PostgreSQL-databaser fra ClusterControl CLI