Raffaeu
Raffaeu

Reputation: 7003

T-SQL CTE, calculation on Previous Row value

I have a CTE that returns a set of rows depending on a pattern in the following way:

Pattern Table (Create 100 rows for the next Monday starting from Start Date)

|                                        |
|--Id--|--Start Date--|--Count--|--Days--|
|                                        |
|--AB--|  01-01-2000  |   100   | Monday |
|                                        |

And I have a function that calculate the next occurring day by passing a starting date as following:

dbo.fx_get_next_occurrence(@startDate DATETIME, @days VARCHAR)

In order to generate the rows I am using a CTE as following:

WITH Occurrences_CTE(Num, Id, StartDate)
AS
(
    SELECT
        SP.n
        ,F.Id
        ,dbo.fx_get_next_occurrence(F.Days, F.StartDate)
    FROM
        #RecurrencePattern AS F 
        INNER JOIN
        dbo.Numbers AS SP
        ON SP.n between 1 and F.[Count]
)

dbo.numbers is a table containing a list of incremental numbers.

Now, when I run the SELECT of course the CTE returns always the same "next date" because the Date is never increased, so this SELECT:

SELECT 
    CTE.Id, CTE.Num, CTE.StartDate
FROM 
    Occurrences_CTE CTE

Generates something like the following result:

|                             |
|--Id--|--Num--|--Start Date--|
|                             |
|--AB--|--001--|--01-03-2000--|
|--AB--|--002--|--01-03-2000--|
|--AB--|--003--|--01-03-2000--|
|--AB--|--004--|--01-03-2000--|
|--AB--|--005--|--01-03-2000--|

Now, despite the function which works as expected, how can I get from the CTE the previous calculated date row so that I can pass this date to my function to move to the next available day starting from the previous one? JOIN the CTE to itself, but then?

In am on SQL 2012 and 2014 so I can use LAG and other functions.

Upvotes: 0

Views: 1004

Answers (2)

John Cappelletti
John Cappelletti

Reputation: 82010

Added a field for Every. So 1 = every week, 2 would be every other week and so on.

Take this for a spin

Declare @Table table (id varchar(25),StartDate Date,Count int,Days varchar(25),Every int)
Insert into @Table values
('AB','2000-01-01',50,'Monday',2),
('CD','2000-01-01',50,'Monday,Friday',2)


Declare @MinDate Date = (Select min(StartDate) from @Table)
Declare @MaxCntr Int  = (Select max(Count) from @Table)
Declare @MaxDate Date = DateAdd(wk,@MaxCntr+10,@MinDate)

;With cteKeyDate As (
    Select KeyDate = @MinDate,KeyDOW = DateName(WEEKDAY,@MinDate),KeyWeek = datepart(WK,@MinDate)
    Union All
    Select KeyDate =  DateAdd(DD, 1, df.KeyDate) ,KeyDOW = DateName(WEEKDAY,DateAdd(DD, 1, df.KeyDate)), KeyWeek= DatePart(WK,DateAdd(DD, 1, df.KeyDate))
    From cteKeyDate DF
    Where DF.KeyDate <= @MaxDate
) 
Select * 
 From (
        Select A.ID
              ,A.StartDate
              ,A.Count
              ,A.Days 
              ,A.Every
              ,KeyDate
              ,KeyDow
              ,KeyWeek
              ,RowNr = Row_Number() over (Partition By A.ID Order By B.KeyDate)
         from @Table A
         Join cteKeyDate B on B.KeyDate>=A.StartDate and Charindex(KeyDOW,Days)>0 
      ) Final
 Where RowNr<=Count and (KeyWeek) % (Every) = 0 
 Option (maxrecursion 32767)

Upvotes: 1

John Cappelletti
John Cappelletti

Reputation: 82010

Perhaps you could simplify this a bit.

I can't speak to your UDF, but it may not be necessary as you may soon see. The cteKeyDate produces more than necessary, but the results are filtered when you join to your data.

You may also notice that I added another row to your sample data which occurs on Monday's and Fridays, but for a count of only 50

Declare @Table table (id varchar(25),StartDate Date,Count int,Days varchar(25))
Insert into @Table values
('AB','2000-01-01',100,'Monday'),
('CD','2000-01-01',50,'Monday,Friday')


Declare @MinDate Date = (Select min(StartDate) from @Table)
Declare @MaxCntr Int  = (Select max(Count) from @Table)
Declare @MaxDate Date = DateAdd(wk,@MaxCntr+10,@MinDate)

;With cteKeyDate As (
    Select KeyDate = @MinDate,KeyDOW = DateName(WEEKDAY,@MinDate)
    Union All
    Select KeyDate =  DateAdd(DD, 1, df.KeyDate) ,KeyDOW = DateName(WEEKDAY,DateAdd(DD, 1, df.KeyDate))
    From cteKeyDate DF
    Where DF.KeyDate <= @MaxDate
) 
Select * 
 From (
        Select A.ID
              ,A.StartDate
              ,A.Count
              ,A.Days 
              ,KeyDate
              ,KeyDow
              ,RowNr = Row_Number() over (Partition By A.ID Order By B.KeyDate)
         from @Table A
         Join cteKeyDate B on B.KeyDate>=A.StartDate and Charindex(KeyDOW,Days)>0 
      ) Final
 Where RowNr<=Count
 Option (maxrecursion 32767)

Returns -- I showed all fields so you can better visualize the results

ID  StartDate   Count   Days            KeyDate     KeyDow  RowNr
AB  2000-01-01  100     Monday          2000-01-03  Monday  1
AB  2000-01-01  100     Monday          2000-01-10  Monday  2
AB  2000-01-01  100     Monday          2000-01-17  Monday  3
AB  2000-01-01  100     Monday          2000-01-24  Monday  4
... 
AB  2000-01-01  100     Monday          2001-11-05  Monday  97
AB  2000-01-01  100     Monday          2001-11-12  Monday  98
AB  2000-01-01  100     Monday          2001-11-19  Monday  99
AB  2000-01-01  100     Monday          2001-11-26  Monday  100
CD  2000-01-01  50      Monday,Friday   2000-01-03  Monday  1
CD  2000-01-01  50      Monday,Friday   2000-01-07  Friday  2
CD  2000-01-01  50      Monday,Friday   2000-01-10  Monday  3
.....
CD  2000-01-01  50      Monday,Friday   2000-06-16  Friday  48
CD  2000-01-01  50      Monday,Friday   2000-06-19  Monday  49
CD  2000-01-01  50      Monday,Friday   2000-06-23  Friday  50

Upvotes: 1

Related Questions