Day-On-Day / Month-On-Month Queries for Temporal Tables

  • Thanks in advance for any tips on this:

    I'm looking for some documentation on how to form queries that track changes over time in a temporal (system-versioned) table. For example, I'd like to query a temporal table and return a result-set for each day with the data's state, so I can visualize the changes over time.

    So if I wanted to query the data as of 3 days ago, I could do this:

    SELECT *

    FROM schemaname.tablename

    FOR SYSTEM_TIME AS OF '2016-12-18 23:59:59'

    Which returns a result set of valid data for Dec 18th near midnight. I'd like to basically multiply this result set by 30 and get sets for each day at midnight.

    Any tips? I'm thinking it'll have to be some sort of join on a temporary table containing the 30 datetime values, but I'm unsure on how I would join the SysStartTime and SysEndTime columns on a date that falls within their range. It's like a fuzzy join of some sort.

    I'm essentially looking for a query syntax that would effectively do this dynamically:

    SELECT *, '2016-12-18 23:59:59' as 'Date'

    FROM schemaname.tablename

    FOR SYSTEM_TIME AS OF '2016-12-18 23:59:59'

    UNION

    SELECT *, '2016-12-19 23:59:59' as 'Date'

    FROM schemaname.tablename

    FOR SYSTEM_TIME AS OF '2016-12-19 23:59:59'

    UNION

    SELECT *, '2016-12-20 23:59:59' as 'Date'

    FROM schemaname.tablename

    FOR SYSTEM_TIME AS OF '2016-12-20 23:59:59'

    UNION

    -- ...

    ...which would give me per-day result sets so I could track changes in my BI tools.

    I could always write a stored procedure to dynamically generate this, but that doesn't feel quite right.

  • Use a number/tally/calendar table to iterate through the days.

    http://www.sqlservercentral.com/articles/T-SQL/62867/

    On a separate note, UNION eliminates duplicate rows, but your result sets are guaranteed unique (different dates). You are adding unnecessary overhead to your query by telling sql server to check for duplicates.

    Wes
    (A solid design is always preferable to a creative workaround)

  • This certainly looks like it would facilitate what I'm trying to do. Thanks!

    Also, great point on the UNION.

  • This feels a bit dirty, but here's what I came up with:

    DECLARE @Sql VARCHAR(max)

    DECLARE @DateStart DATETIME

    DECLARE @DateEnd DATETIME

    SELECT @DateStart = MIN(SysStartTime)

    ,@DateEnd = GETDATE()

    FROM SchemaName.TableName

    SELECT @Sql = COALESCE(@Sql + ' ', '') + UnionQuery

    FROM (

    SELECT 'SELECT *, CAST(''' + CAST(TallyDate AS VARCHAR(20)) + ''' AS DATE) as ''DateKey'' FROM SchemaName.TableName FOR SYSTEM_TIME AS OF ''' + CAST(TallyDate AS VARCHAR(20)) + ''' UNION ALL ' AS 'UnionQuery'

    FROM (

    SELECT CAST(t.N - 1 + @DateStart AS DATE) AS 'TallyDate'

    FROM dbo.Tally t

    WHERE t.N - 1 + @DateStart <= @DateEnd

    ) dates

    ) s

    SELECT @Sql = REVERSE(STUFF(REVERSE(@Sql), 1, 10, ''))

    EXEC (@Sql)

    It actually works like a charm. Would love to get some feedback on this, because it feels like I've just used a rocket launcher to smite a mosquito.

  • Sonny Childs (12/21/2016)


    This feels a bit dirty, but here's what I came up with:

    DECLARE @Sql VARCHAR(max)

    DECLARE @DateStart DATETIME

    DECLARE @DateEnd DATETIME

    SELECT @DateStart = MIN(SysStartTime)

    ,@DateEnd = GETDATE()

    FROM SchemaName.TableName

    SELECT @Sql = COALESCE(@Sql + ' ', '') + UnionQuery

    FROM (

    SELECT 'SELECT *, CAST(''' + CAST(TallyDate AS VARCHAR(20)) + ''' AS DATE) as ''DateKey'' FROM SchemaName.TableName FOR SYSTEM_TIME AS OF ''' + CAST(TallyDate AS VARCHAR(20)) + ''' UNION ALL ' AS 'UnionQuery'

    FROM (

    SELECT CAST(t.N - 1 + @DateStart AS DATE) AS 'TallyDate'

    FROM dbo.Tally t

    WHERE t.N - 1 + @DateStart <= @DateEnd

    ) dates

    ) s

    SELECT @Sql = REVERSE(STUFF(REVERSE(@Sql), 1, 10, ''))

    EXEC (@Sql)

    It actually works like a charm. Would love to get some feedback on this, because it feels like I've just used a rocket launcher to smite a mosquito.

    With regard to the part of your post I bolded, I just have to know when that became a bad thing... 😉

    Steve (aka sgmunson) 🙂 🙂 🙂
    Rent Servers for Income (picks and shovels strategy)

  • Sonny Childs (12/21/2016)


    Would love to get some feedback on this, because it feels like I've just used a rocket launcher to smite a mosquito.

    Tally table provides the power of a rocket launcher with the labor of a pop cap.

    I've put some comments inline. However, I don't have any temporal tables in my current environment (yet) so I can't validate the syntax of the FOR clause.

    DECLARE @Sql VARCHAR(max)=''; --Set the blank string here so you don't need the null test later;

    DECLARE @DateStart DATETIME

    DECLARE @DateEnd DATETIME = GETDATE();

    SELECT @DateStart = DATEADD(DAY,-1,MIN(SysStartTime)) --subtract a single day here rather than for every row within the worker query.

    FROM SchemaName.TableName

    SELECT @Sql = @SQL + UnionQuery --Can remove coalesce because variable was set to empty string in declaration

    FROM (

    SELECT 'SELECT *, ''' + TallyDate + ''' as ''DateKey'' FROM SchemaName.TableName FOR SYSTEM_TIME AS OF ''' + TallyDate + ''' UNION ALL ' AS UnionQuery

    FROM (

    SELECT CONVERT(CHAR(8),DATEADD(DAY,t.N,@DateStart),112) AS TallyDate

    --built in date functions are very efficient and easier to read,

    --converting to a string here saves multiple string conversions in the outer query

    --style 112 is universal date format for sql server

    FROM dbo.Tally AS T

    WHERE N < DATEDIFF(DAY,@DateStart,@DateEnd)

    --expressions on variables are much quicker,

    --fields and expressions are on opposite sides of comparison which helps performance

    ) dates

    ) s

    SELECT @Sql = LEFT(@Sql,LEN(@sql) -10) --Personal preference, I think this is easier to read than Reverse/Stuff/Reverse

    EXEC (@Sql)

    Hopefully it still executes for you. 🙂

    On a separate note, I highly recommend using midnight rather than 23:59:59. It is so much easier to work, think and converse when you don't have the time component included.

    Wes
    (A solid design is always preferable to a creative workaround)

  • whenriksen (12/21/2016)


    Tally table provides the power of a rocket launcher with the labor of a pop cap.

    I've put some comments inline. However, I don't have any temporal tables in my current environment (yet) so I can't validate the syntax of the FOR clause.

    ...

    Hopefully it still executes for you. 🙂

    On a separate note, I highly recommend using midnight rather than 23:59:59. It is so much easier to work, think and converse when you don't have the time component included.

    Thanks, whenriksen.

    That's some fantastic feedback, I'll certainly include these.

    Oddly enough, the performance was identical either way, after running some benchmarks, but I certainly need to keep those optimizations in mind in general.

Viewing 7 posts - 1 through 6 (of 6 total)

You must be logged in to reply to this topic. Login to reply