sqlsql-servertimelinetemporal-database

Creating a timeline with multiple tables in SQL Server


I have the following 3 tables:

CREATE TABLE Jedi  (
jediId INT NOT null,
name nvarchar(100) NOT NULL,
jediRank nvarchar(100) NOT NULL,
fromDate Date NOT NULL,
toDate Date NOT NULL
)

INSERT INTO Jedi(jediId, name, jediRank, fromdate, todate)
SELECT 666 jediId, 'A.Skywalker' name, 'padawan' jediRank, '1990-01-01' fromDate, '1999-12-31' toDate Union
SELECT 666 jediId, 'A.Skywalker' name, 'Jedi Knight' jediRank, '2000-01-01' fromDate, '2005-06-17' toDate Union
SELECT 666 jediId, 'A.Skywalker' name, 'Jedi Master' jediRank, '2005-06-18' fromDate, '2005-09-28' toDate Union
SELECT 666 jediId, 'D.Vader' name, 'Sith Apprentice' jediRank, '2005-09-29' fromDate, '9999-12-31' toDate  
ORDER BY jediId, fromdate, todate

CREATE TABLE lightsaberColors (
jediId int, 
lightsaberColor nvarchar(100) NOT NULL,
fromDate Date NOT NULL,
toDate Date NOT NULL
)

INSERT INTO lightsaberColors(jediId, lightsaberColor, fromdate, todate)
SELECT 666, 'blue' color, '1990-01-01' fromdate, '1992-01-01' todate UNION
SELECT 666, 'green' color, '1992-01-02' fromdate, '1992-04-15' todate UNION
SELECT 666, 'blue' color, '1992-04-16' fromdate, '2005-09-30' todate UNION
SELECT 666, 'red' color, '2005-10-01' fromdate, '9999-12-31' todate
ORDER BY fromdate, todate

CREATE TABLE mechanicalbodyparts (
jediId int, 
mechanicalBodyPart nvarchar(100) NOT NULL,
fromDate Date NOT NULL,
toDate Date NOT NULL
)
INSERT INTO mechanicalbodyparts(jediId, mechanicalBodyPart,fromDate, toDate)
SELECT 666, 'N/A' mechanicalBodyPart, '1990-01-01' fromdate, '1998-05-13' todate UNION
SELECT 666, 'hand and arm' mechanicalBodyPart, '1998-05-14' fromdate, '2005-09-29' todate UNION
SELECT 666, 'pretty much everything' mechanicalBodyPart, '2005-09-30' fromdate, '9999-12-31' todate
ORDER BY fromdate, todate

 SELECT * From jedi ORDER BY fromdate
 SELECT * From lightsaberColors ORDER BY fromdate
 SELECT * From mechanicalbodyparts ORDER BY fromdate

and what I am trying to find is an optimal way of combining all this data into a single table that is essentially a timeline for each jedi.

The table I would end up in this case is:

CREATE TABLE JediTimeline(
jediId Int NOT null,
fromDate date NOT null,
toDate date NOT NULL,
name nvarchar(100) NOT NULL,
jediRank nvarchar(100) NOT NULL, 
mechanicalBodyPart nvarchar(100) NOT NULL,
lightsaberColor nvarchar(100) NOT NULL
)

INSERT INTO JediTimeline (jediId, fromDate, toDate, name, jediRank, mechanicalBodyPart, lightsaberColor)
Select 666, '1990-01-01', '1992-01-01', 'A.Skywalker', 'padawan', 'N/A', 'blue' Union
Select 666, '1992-01-02', '1992-04-15', 'A.Skywalker', 'padawan', 'N/A', 'green' Union
Select 666, '1992-04-16', '1998-05-13', 'A.Skywalker', 'padawan', 'N/A', 'blue' Union
Select 666, '1998-05-14', '1999-12-31', 'A.Skywalker', 'padawan', 'hand and arm', 'blue' Union
Select 666, '2000-01-01', '2005-06-17', 'A.Skywalker', 'Jedi Knight', 'hand and arm', 'blue' Union
Select 666, '2005-06-18', '2005-09-28', 'A.Skywalker', 'Jedi Master', 'hand and arm', 'blue' Union
Select 666, '2005-09-29', '2005-09-29', 'D.Vader', 'Sith Apprentice', 'hand and arm', 'blue' Union
Select 666, '2005-09-30', '2005-09-30', 'D.Vader', 'Sith Apprentice', 'pretty much everything', 'blue' Union
Select 666, '2005-10-01', '9999-12-31', 'D.Vader', 'Sith Apprentice', 'pretty much everything', 'red'  
ORDER BY 1,2,3


SELECT *
FROM JediTimeline

Now, I have come up with a solution for this but it uses a cursor which works fine in this situation but its not ideal when the data I'm working with is significantly larger.

I am working with SQL Server 2012.

On a side note, is this an example of an 'Islands' problem?


Solution

  • All the DATEADDs could be avoided if you run a >= and < scheme, instead of >= and <= (on fromDate and toDate).

    with
        UniqueCutoffDates as (
            select distinct fromDate as cutoff, jediId from Jedi union
            select distinct fromDate,           jediId from lightsaberColors union
            select distinct fromDate,           jediId from mechanicalbodyparts union
    
            select distinct DATEADD(d,1,toDate), jediId from Jedi                where toDate <> '9999-12-31' union
            select distinct DATEADD(d,1,toDate), jediId from lightsaberColors    where toDate <> '9999-12-31' union
            select distinct DATEADD(d,1,toDate), jediId from mechanicalbodyparts where toDate <> '9999-12-31'
        )
    
    select
        jedi.jediId,
        dates.cutoff as fromDate,
        DATEADD(d,-1,LEAD(dates.cutoff,1,'9999-12-31') OVER (partition by jedi.jediId order by dates.cutoff)) as toDate,
        jedi.name,
        jedi.jediRank,
        body.mechanicalBodyPart,
        colors.lightsaberColor
    from
        UniqueCutoffDates dates
        inner join Jedi on
            jedi.fromDate <= dates.cutoff and
            Jedi.toDate > DATEADD(d,-1,dates.cutoff) and
            jedi.jediId = dates.jediId
        inner join lightsaberColors colors on
            colors.fromDate <= dates.cutoff and
            colors.toDate > DATEADD(d,-1,dates.cutoff) and
            colors.jediId = jedi.jediId
        inner join mechanicalbodyparts body on
            body.fromDate <= dates.cutoff and
            body.toDate > DATEADD(d,-1,dates.cutoff) and
            body.jediId = jedi.jediId