• subscribe
September 29, 2008 12:00 AM

Query Temporal Data

Work around SQL Server’s incomplete support
SQL Server Pro
InstantDoc ID #99874
Downloads
99874.zip

The first step in my solution defining the CTE Timestamps is almost identical to Davide’s Timestamps CTE, except that mine uses a UNION operation as the last set operation instead of UNION ALL. This means that duplicate timestamps are removed at this step in my solution.

In the second step (in my solution, defining CTE TSRN—timestamps with row numbers), I didn’t need to use the combination of the DENSE_RANK function and the DISTINCT clause. Instead, I simply assigned row numbers to position the timestamps chronologically.

The third step in my solution (i.e., defining the Intervals CTE) is identical to Davide’s. Then our solutions start to differ. My solution reaches the last step at this point, implemented by the outer query. The outer query joins the Intervals CTE, which contains all distinct possible periods with each of the attribute tables (Phones and Addresses, in our case) containing validity periods, to match all overlapping validity periods to each possible period. A LEFT OUTER JOIN is used because there’s no guarantee that each possible period will find a match in each attribute table.

Maintaining the Solutions
As you can see from this problem, treatment of time periods can be challenging and quite tricky—especially because SQL Server has incomplete support for temporal data. If you want to use Davide’s or my solution to support new attributes, you’ll need to create a new table for each attribute, then revise the solution to include the attributes. For either solution, you’ll need to revise the Timestamps CTE, adding the timestamps from the new attribute table. For Davide’s solution, you’ll need to revise the UnifiedValues CTE, adding a UNION ALL set operation and a query against the new attribute table to pull the validity periods of the new attribute. In addition, you’ll need to add the new attribute name to the IN clause of the PIVOT operator in the outer query. For my solution, you’ll need to revise the outer query, adding a LEFT OUTER JOIN to the new attribute table.

Did you manage to come up with a different solution than mine or Davide’s? If so, share your solution by posting a comment to this article.



ARTICLE TOOLS

Comments
  • Vince
    4 years ago
    Oct 17, 2008

    Terrific! I was able to put this to use immediately. However, I needed to track temporal data by object (location and status in my case) so I added a piece identifier to solution 3 like so:

    with Timestamps as
    (
    select PieceId, StartDate as DateVal from PieceLocation
    union all select PieceId, coalesce(EndDate, '12/29/2029') from PieceLocation
    union all select PieceId, StartDate from PieceStatus
    union select PieceId, coalesce(EndDate, '12/29/2029') from PieceStatus
    ),
    TimestampsRN as
    (
    select PieceId, DateVal, row_number() over (order by PieceId, DateVal) as Position
    from Timestamps
    ),
    Intervals as
    (
    select cur.PieceId, StartDate = cur.DateVal, EndDate = nxt.DateVal
    from TimestampsRN cur
    inner join TimestampsRN nxt on cur.PieceId = nxt.PieceId
    and nxt.Position = cur.Position + 1
    )
    select i.StartDate, i.EndDate, i.PieceId, s.StatusId, l.LocationId
    from Intervals i
    left join PieceStatus s on i.PieceId = s.PieceId
    and coalesce(s.EndDate, '12/31/2029') > i.StartDate
    and s.StartDate < i.EndDate
    left join PieceLocation l on i.PieceId = l.PieceId
    and coalesce(l.EndDate, '12/31/2029') > i.StartDate
    and l.StartDate < i.EndDate
    go

    -- I hope the formatting carries over into the comment. Here goes...

  • Christian
    4 years ago
    Oct 08, 2008

    -- Slightly different creation of the date ranges:
    WITH StartDates AS ( -- Collect all "period delimiters"
    SELECT StartDt FROM dbo.Phones
    UNION SELECT StartDt FROM dbo.Addresses
    ),Ranges AS ( -- build the ranges from the collected delimiters
    SELECT StartDt, (SELECT ISNULL(MIN(StartDt),99999999) EndDt FROM StartDates WHERE StartDT > StartDates1.StartDt) EndDt
    FROM StartDates AS StartDates1
    )
    -- Join the rows to the ranges
    SELECT P.Phone, A.Address, R.StartDt, R.EndDt
    FROM Ranges R
    LEFT JOIN Phones P ON NOT P.StartDt >= R.EndDt AND NOT ISNULL(P.EndDt,99999999) <= R.StartDt
    LEFT JOIN Addresses A ON NOT A.StartDt >= R.EndDt AND NOT ISNULL(A.EndDt,99999999) <= R.StartDt

    Cheers,
    Chris

You must log on before posting a comment.

Are you a new visitor? Register Here