sql >> Databasteknik >  >> RDS >> Sqlserver

Date Range Intersection Splitting i SQL

Problemet du kommer att ha med det här problemet är att när datamängden växer kommer lösningarna för att lösa det med TSQL inte skalas bra. Nedanstående använder en serie tillfälliga tabeller byggda i farten för att lösa problemet. Den delar upp varje datumintervall i sina respektive dagar med hjälp av en siffertabell. Det är här det inte kommer att skalas, främst på grund av dina öppna NULL-värden som verkar vara oändliga, så du måste byta in ett fast datum långt in i framtiden som begränsar konverteringsintervallet till en möjlig tidsperiod. Du kan sannolikt se bättre prestanda genom att skapa en tabell över dagar eller en kalendertabell med lämplig indexering för optimerad rendering av varje dag.

När intervallen är uppdelade slås beskrivningarna samman med XML PATH så att varje dag i intervallserien har alla beskrivningar för sig. Radnumrering efter person-ID och datum gör att den första och sista raden i varje intervall kan hittas med hjälp av två INTE FINNS-kontroller för att hitta instanser där en tidigare rad inte finns för en matchande person-ID och beskrivningsuppsättning, eller där nästa rad inte finns. t existerar för en matchande person-ID och beskrivningsuppsättning.

Denna resultatuppsättning numreras sedan om med ROW_NUMBER så att de kan paras ihop för att bygga de slutliga resultaten.

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

*/

Ovanstående lösning kommer också att hantera luckor mellan ytterligare beskrivningar också, så om du skulle lägga till ytterligare en beskrivning för PersonID 18 och lämnar ett gap:

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

Det kommer att fylla luckan på lämpligt sätt. Som påpekats i kommentarerna bör du inte ha namninformation i den här tabellen, den bör normaliseras till en persontabell som kan anslutas till i slutresultatet. Jag simulerade den här andra tabellen genom att använda en SELECT DISTINCT för att bygga en tillfällig tabell för att skapa den JOIN.



  1. SQL-fråga som involverar gruppera efter och går med

  2. QueryBuilder/Doctrine Välj gå med i grupp

  3. MySQL lägger till fält i en Enum

  4. Hur man hämtar 2 gånger i MYSQL PDO utan FETCHALL