Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Dzielenie przecięcia zakresów dat w SQL

Problem, który będziesz mieć z tym problemem, polega na tym, że wraz ze wzrostem zestawu danych rozwiązania do jego rozwiązania za pomocą TSQL nie będą dobrze skalowane. Poniżej wykorzystano szereg tymczasowych tabel tworzonych w locie, aby rozwiązać problem. Dzieli każdy wpis z zakresu dat na odpowiadające mu dni za pomocą tabeli liczb. W tym miejscu nie będzie się skalować, głównie ze względu na otwarte wartości NULL w zakresie otwartym, które wydają się być nieskończonością, więc musisz zamienić ustaloną datę daleko w przyszłość, która ogranicza zakres konwersji do możliwego czasu. Możesz prawdopodobnie uzyskać lepszą wydajność, tworząc tabelę dni lub tabelę kalendarza z odpowiednim indeksowaniem, aby zoptymalizować renderowanie każdego dnia.

Po podzieleniu zakresów opisy są scalane przy użyciu ścieżki XML PATH, tak aby każdy dzień w serii zakresów zawierał wszystkie wymienione opisy. Numeracja wierszy według PersonID i Date pozwala na znalezienie pierwszego i ostatniego wiersza każdego zakresu przy użyciu dwóch sprawdzeń NOT EXISTS w celu znalezienia przypadków, w których poprzedni wiersz nie istnieje dla pasującego zestawu PersonID i Description lub gdzie następny wiersz nie istnieje. Nie istnieje dla pasującego zestawu PersonID i Description.

Ten zestaw wyników jest następnie numerowany przy użyciu ROW_NUMBER, aby można je było sparować w celu utworzenia ostatecznych wyników.

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

*/

Powyższe rozwiązanie obsługuje również luki między dodatkowymi opisami, więc jeśli chcesz dodać kolejny opis dla PersonID 18, pozostawiając lukę:

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

Odpowiednio wypełni lukę. Jak wskazano w komentarzach, nie powinieneś mieć informacji o nazwisku w tej tabeli, powinna ona zostać znormalizowana do tabeli osób, do której można połączyć się w wyniku końcowym. Zasymulowałem tę inną tabelę, używając SELECT DISTINCT do zbudowania tabeli tymczasowej, aby utworzyć to JOIN.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Czy powinienem ustawić maksymalny rozmiar puli w parametrach połączenia z bazą danych? Co się stanie, jeśli tego nie zrobię?

  2. Jak ROW_NUMBER() działa w SQL Server

  3. Usuń zduplikowane wartości w komórce SQL Server

  4. Utwórz tabelę z kompresją w SQL Server (T-SQL)

  5. OR nie jest obsługiwany z instrukcją CASE w SQL Server