Search code examples
sql-serverdatabaserdbmsdynamic-pivot

Dynamic Pivot on sql data


I have two tables as shown below

The master Table

ID               keyword_tags
----------- ---------------------------------------------
10932        international foo data

and a child table(join clause on id = fk_id)

fk_id       date_value               observ_value
----------- -----------------------  ----------------------
10932       2009-01-01 00:00:00.000  331.888888888
10932       2008-06-01 00:00:00.000  301.888888888
10932       2008-01-01 00:00:00.000  321.777777777
10932       2007-01-01 00:00:00.000  288.449066162
10932       2006-01-01 00:00:00.000  259.789733887

Output required is

ID      keyword_tags             Latest_Value    Latest_Change  Annual_Change
------  ----------------------   -------------   -------------  --------------- 
10932   international foo data   331.888888888   30.000000000   10.111111111

where

Latest_Change = observ_value(of most recent date_value) - observ_value(of next most recent date_value)
Annual_Change = observ_value(of most recent date_value) - observ_value(of recent date_value - 1 year)

How do i achieve this using sql-server?


Solution

  • This should work:

    I create the tables as

    create table master_table(
      id            int not null identity( 1, 1 )
    , keyword_tags  nvarchar( 127 ) not null
    
    , constraint "master_PK" primary key clustered( "id" ) );
    
    create table child_table(
      id            int not null identity( 1, 1 )
    , fk_id         int not null
    , date_value    datetime not null
    , observ_value  float not null
    
    , constraint "child_PK" primary key clustered( "id" )
    , constraint "child_FK_fkid" foreign key ( "fk_id" )
                                     references master_table( "id" ) );
    

    Let's insert some data:

    insert into master_table select N'international foo data';
    insert into master_table select N'national baz data';
    
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 1, N'2009-01-01T00:00:00.000', 331.888888888
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 1, N'2008-06-01T00:00:00.000', 301.888888888
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 1, N'2008-01-01T00:00:00.000', 321.777777777
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 1, N'2007-01-01T00:00:00.000', 288.449066162
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 1, N'2006-01-01T00:00:00.000', 259.789733887;
    
    
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 2, N'2003-07-01T00:00:00.000', 142.0
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 2, N'2002-07-02T00:00:00.000', 123.0
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 2, N'2002-07-01T00:00:00.000', 117.0
    insert into child_table--( "fk_id", "date_value", "observ_value" )
    select 2, N'2001-01-01T00:00:00.000', 107.0;
    

    Now for the interesting part:

    with currRow as (
        select fk_id, MAX( date_value ) as currDate
        from child_table
        group by fk_id )
    select currRow.fk_id, ct.ID, currRow.currDate, ct.observ_value
    into #currRow
    from currRow
    inner join child_table as ct
        on ct.date_value = currRow.currDate;
    
    with lastEntry as (
        select olderRows.fk_id, MAX( olderRows.date_value ) as date_value
        from #currRow as currRow
        inner join child_table as olderRows
            on  olderRows.fk_id = currRow.fk_id
            and olderRows.date_value < currRow.currDate
        group by olderRows.fk_id ),
    oneYearAgo as (
        select olderRows.fk_id, MAX( olderRows.date_value ) as date_value
        from #currRow as currRow
        inner join child_table as olderRows
            on  olderRows.fk_id = currRow.fk_id
            and olderRows.date_value <= DATEADD( YEAR, -1, currRow.currDate )
        group by olderRows.fk_id )
    select
          master_table.*
        , currRow.ID as currID
        , currRow.currDate
    , currRow.observ_value as currObservValue
        , lastData.id as lastPriorID
        , lastData.date_value as lastPriorDateValue
        , lastData.observ_value as lastPriorObservValue
        , oneYearAgoData.id as oneYearAgoID
        , oneYearAgoData.date_value as oneYearAgoDateValue
        , oneYearAgoData.observ_value as oneYearAgoObservValue
    from #currRow as currRow
    inner join master_table
        on master_table.id = currRow.fk_id
    inner join lastEntry
        on lastEntry.fk_id = currRow.fk_id
    inner join child_table as lastData
        on  lastData.fk_id = lastEntry.fk_id
        and lastData.date_value = lastEntry.date_value
    inner join oneYearAgo
        on  oneYearAgo.fk_id = currRow.fk_id
    inner join child_table as oneYearAgoData
        on  oneYearAgoData.fk_id = oneYearAgo.fk_id
        and oneYearAgoData.date_value = oneYearAgo.date_value
    

    Calculating your Latest_Change and Annual_Change from these is left as an exercise for the reader.