Sub query to return data recursively?

  • I have 1 table that I am trying to build a record out of. Will say for sample purposes that the table has the following structure:

    Name: MyTable

    Columns:

    EmployeeID int,

    DataYear int,

    YearlyWage float

    I need to report off of this table so that each record returns 4 columns as follows:

    EmployeeID | DataYear | YearlyWage | FiveYearWage

    So I need to query the table and get the total wage for the last 5 years plus the current wage. I'm thinking I need to do a sub query or something of the sort. Here is some test data and required output:

    -- This is the test data

    DECLARE @MyTable TABLE

    (

    EmployeeID INT,

    DataYear int,

    YearlyWage float

    )

    INSERT INTO @MyTable (EmployeeID,DataYear,YearlyWage)

    SELECT 1001,1990,25000 UNION ALL

    SELECT 1001,1991,25000 UNION ALL

    SELECT 1001,1992,30000 UNION ALL

    SELECT 1001,1993,30000 UNION ALL

    SELECT 1001,1994,40000 UNION ALL

    SELECT 1001,1995,40000 UNION ALL

    SELECT 1001,1996,50000 UNION ALL

    SELECT 1001,1997,50000 UNION ALL

    SELECT 1001,1998,60000

    -- This is desired output

    SELECT 1001,1990,25000,25000 UNION ALL

    SELECT 1001,1991,25000,50000 UNION ALL

    SELECT 1001,1992,30000,80000 UNION ALL

    SELECT 1001,1993,30000,110000 UNION ALL

    SELECT 1001,1994,40000,150000 UNION ALL

    SELECT 1001,1995,40000,165000 UNION ALL

    SELECT 1001,1996,50000,190000 UNION ALL

    SELECT 1001,1997,50000,210000 UNION ALL

    SELECT 1001,1998,60000,240000

    This is my attempted solution, which of course fails

    -- This is the test data

    DECLARE @MyTable TABLE

    (

    EmployeeID INT,

    DataYear int,

    YearlyWage float

    )

    INSERT INTO @MyTable (EmployeeID,DataYear,YearlyWage)

    SELECT 1001,1990,25000 UNION ALL

    SELECT 1001,1991,25000 UNION ALL

    SELECT 1001,1992,30000 UNION ALL

    SELECT 1001,1993,30000 UNION ALL

    SELECT 1001,1994,40000 UNION ALL

    SELECT 1001,1995,40000 UNION ALL

    SELECT 1001,1996,50000 UNION ALL

    SELECT 1001,1997,50000 UNION ALL

    SELECT 1001,1998,60000

    -- This is my attempt

    SELECT

    MT.EmployeeID,

    MT.DataYear,

    MT.YearlyWage,

    FiveYearWage =

    (SELECT

    SUM(CASE WHEN DataYear BETWEEN MT.DataYear - 4 AND MT.DataYear THEN YearlyWage ELSE 0 END)

    FROM

    @MyTable AS TEMP

    WHERE

    TEMP.EmployeeID = MT.EmployeeID

    GROUP BY TEMP.EmployeeID)

    FROM

    @MyTable MT

  • SELECT EmployeeID,

    DataYear,

    YearlyWage,

    FiveYearWage = (SELECT SUM(YearlyWage)

    FROM @MyTable t2

    WHERE t2.EmployeeID = t1.EmployeeID

    AND t2.DataYear <= t1.DataYear

    AND t1.DataYear - t2.DataYear < 5)

    FROM @MyTable t1;

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Or without a recursive query

    select t1.EmployeeID, t1.DataYear, t1.YearlyWage, Sum(t2.YearlyWage)

    from @MyTable t1

    join @MyTable t2 on t2.EmployeeID = t1.EmployeeID

    and t2.DataYear between t1.DataYear - 4 and t1.DataYear

    group by t1.EmployeeID, t1.DataYear, t1.YearlyWage

    order by t1.EmployeeID, t1.DataYear

    /T

  • WayneS (10/4/2010)


    SELECT EmployeeID,

    DataYear,

    YearlyWage,

    FiveYearWage = (SELECT SUM(YearlyWage)

    FROM @MyTable t2

    WHERE t2.EmployeeID = t1.EmployeeID

    AND t2.DataYear <= t1.DataYear

    AND t1.DataYear - t2.DataYear < 5)

    FROM @MyTable t1;

    Thanks, this is what I was looking for. Just curious though, I thought you always had to use a GROUP BY clause when using an aggregate. Is this dismissed because it is assumed a sub query groups based on the join to the main query?

  • loki1049 (10/5/2010)


    WayneS (10/4/2010)


    SELECT EmployeeID,

    DataYear,

    YearlyWage,

    FiveYearWage = (SELECT SUM(YearlyWage)

    FROM @MyTable t2

    WHERE t2.EmployeeID = t1.EmployeeID

    AND t2.DataYear <= t1.DataYear

    AND t1.DataYear - t2.DataYear < 5)

    FROM @MyTable t1;

    Thanks, this is what I was looking for. Just curious though, I thought you always had to use a GROUP BY clause when using an aggregate. Is this dismissed because it is assumed a sub query groups based on the join to the main query?

    Nope, it's dismissed because you're not grouping on anything. Your where clause is limited (and reran) for each of the t1.columns, so your aggregate is determined based on that.

    The other way to do it would be to use it as a subquery and group by on EmployeeID and DataYear, and have a 5 year for each datayear, then link back, which would require the group by.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Ok I got this thing working mostly. The problem is I simplified my example too much and now can't seem to get it to work. The table actaully holds a value for quarter (1-4) for each year, and depending on the quarter I have to look back and get 5years worth of quarters starting at the current records quarter. I also need to get the previous value for the last 5 quarters, which I have figured out thanks to the help. But I'm not too sure how to go about getting five years worth of previous quarters as a total...

    So this is what I am working with now, changed the EmployeeID to just employee so I could see a name instead for the moment. Also added the quarters to the table.

    -- This is the test data

    DECLARE @MyTable TABLE

    (

    Employee nvarchar(4),

    DataYear int,

    DataQuarter int,

    Dose float

    )

    INSERT INTO @MyTable (Employee,DataYear,Dose,DataQuarter)

    SELECT 'Mike',2010,0.568,4 UNION ALL

    SELECT 'Mike',2010,0.698,3 UNION ALL

    SELECT 'Mike',2010,0.223,2 UNION ALL

    SELECT 'Mike',2010,0.111,1 UNION ALL

    SELECT 'Mike',2009,0.203,4 UNION ALL

    SELECT 'Mike',2009,0.869,3 UNION ALL

    SELECT 'Mike',2009,0.438,2 UNION ALL

    SELECT 'Mike',2009,0.456,1 UNION ALL

    SELECT 'Mike',2008,0.656,4 UNION ALL

    SELECT 'Mike',2008,0.889,3 UNION ALL

    SELECT 'Mike',2008,0.277,2 UNION ALL

    SELECT 'Mike',2008,0.396,1 UNION ALL

    -- Second test employee

    SELECT 'Dave',2010,0.1,4 UNION ALL

    SELECT 'Dave',2010,0.666,3 UNION ALL

    SELECT 'Dave',2010,0.1,2 UNION ALL

    SELECT 'Dave',2010,0.1,1 UNION ALL

    SELECT 'Dave',2009,0.1,4 UNION ALL

    SELECT 'Dave',2009,0.1,3 UNION ALL

    SELECT 'Dave',2009,0.1,2 UNION ALL

    SELECT 'Dave',2009,0.1,1 UNION ALL

    SELECT 'Dave',2008,NULL,4 UNION ALL

    SELECT 'Dave',2008,NULL,3 UNION ALL

    SELECT 'Dave',2008,NULL,2 UNION ALL

    SELECT 'Dave',2008,NULL,1

    -- And would need to get a running total of 5 years worth of quarters back

    SELECT

    FiveYear = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear <= t1.DataYear AND t1.DataYear - t2.DataYear < 5),0),

    Employee,

    DataYear,

    DataQuarter,

    Quarter1 = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND (t2.DataYear = t1.DataYear AND t2.DataQuarter = t1.DataQuarter)),0),

    Quarter2 = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear = (CASE WHEN t1.DataQuarter - 1 = 0 THEN t1.DataYear - 1 ELSE t1.DataYear END)

    AND t2.DataQuarter = (CASE WHEN t1.DataQuarter - 1 = 0 THEN 4 ELSE t1.DataQuarter - 1 END)),0),

    Quarter3 = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear = (CASE WHEN t1.DataQuarter - 2 <= 0 THEN t1.DataYear - 1 ELSE t1.DataYear END)

    AND t2.DataQuarter = (CASE WHEN t1.DataQuarter - 2 <= 0 THEN 4 + (t1.DataQuarter - 2) ELSE t1.DataQuarter - 2 END)),0),

    Quarter4 = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear = (CASE WHEN t1.DataQuarter - 3 <= 0 THEN t1.DataYear - 1 ELSE t1.DataYear END)

    AND t2.DataQuarter = (CASE WHEN t1.DataQuarter - 3 <= 0 THEN 4 + (t1.DataQuarter - 3) ELSE t1.DataQuarter - 3 END)),0),

    Quarter5 = IsNull((SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear = t1.DataYear-1

    AND t2.DataQuarter = t1.DataQuarter),0)

    FROM @MyTable t1

    ORDER BY Employee DESC, Datayear DESC, Dataquarter desc

    I obviously don't have the correct method for doing the 5 year part, I have been playing around with it but can't figure out how. Also, if anyone thinkgs this is a horrible query I am open to suggestions. I want to create a view out of this so I can report indiviuals based on a query of a year and quarter and have it all sit in one view.

    EDIT: Thanks for the help so far.

  • I got it, damn that took a long time. Thanks for the examples I was dead in the water trying this all yesterday

    To get 5 years worth of quarters data

    FiveYear = (SELECT SUM(IsNull(Dose,0)) FROM @MyTable t2 WHERE t2.Employee = t1.Employee

    AND t2.DataYear BETWEEN t1.DataYear - 5 AND t1.DataYear

    AND(t2.DataQuarter BETWEEN

    (CASE WHEN t2.DataYear = t1.DataYear - 5 THEN t1.DataQuarter+1 ELSE 1 END) AND (CASE WHEN t2.DataYear = t1.DataYear THEN t1.DataQuarter ELSE 4 END))

    )

  • loki1049 (10/5/2010)


    WayneS (10/4/2010)


    SELECT EmployeeID,

    DataYear,

    YearlyWage,

    FiveYearWage = (SELECT SUM(YearlyWage)

    FROM @MyTable t2

    WHERE t2.EmployeeID = t1.EmployeeID

    AND t2.DataYear <= t1.DataYear

    AND t1.DataYear - t2.DataYear < 5)

    FROM @MyTable t1;

    Thanks, this is what I was looking for. Just curious though, I thought you always had to use a GROUP BY clause when using an aggregate. Is this dismissed because it is assumed a sub query groups based on the join to the main query?

    No, you have to use a GROUP BY when mixing columns and aggregates. If you not using any columns outside of the aggregate then the GROUP BY isn't needed.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Right.. I knew that. Clearly I am trying to make this too complicated in my mind. Thanks

  • SELECT t1.EmployeeID

    ,t1.DataYear

    ,t1.YearlyWage

    ,t3.total

    FROM@MyTable t1

    CROSS APPLY(SELECT SUM(t2.YearlyWage) AS Total

    FROM@MyTable t2

    WHERE t2.EmployeeID = t1.EmployeeID

    ANDt2.DataYear < = t1.DataYear

    GROUP BY t2.EmployeeID) AS t3

  • Oh, i've missed 5-year condition... :crying:

  • Victor,

    Just change your date comparison from:

    AND t2.DataYear < = t1.DataYear

    to

    AND t2.DataYear BEWEEN t1.DataYear - 5 And T1.DataYear

    Todd Fifield

  • Thanks, Todd!

Viewing 13 posts - 1 through 12 (of 12 total)

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