1

I have a task table where I want to create a view on that can do the following: 1. Iterate through each row 2. Explode each day between start and end date into a new row 3. Insert the average task work in a new column

This is the table:

CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
)

INSERT INTO #InputTABLE VALUES('2298aas','2018-06-06','2018-06-12',200);

I have gotten so far as to get 2 and 3 solved, but I really struggle with the iteration part. The code below will fail if more that one row is present in the InputTABLE:

CREATE TABLE #OutputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
)

DECLARE @taskid varchar (200)
DECLARE @cnt int
DECLARE @startDate datetime
DECLARE @endDate datetime
DECLARE @incr int
DECLARE @tempDate datetime 
DECLARE @work int
DECLARE @averagework int

SET @taskid=(Select TaskID from #InputTABLE)
SET @startDate=(Select startdate from #InputTABLE)
SET @endDate=(Select enddate from #InputTABLE)
SET @cnt=DATEDIFF(dy,@startDate,@endDate)
SET @incr=0

SET @tempDate=DATEADD(dy,@incr,Cast(@startDate As datetime))
SET @work=(Select TaskWork from #InputTABLE)
SET @averagework= @work/@cnt

WHILE @cnt>=0
BEGIN

   IF @cnt = 0 
      BEGIN
         INSERT INTO #OutputTABLE VALUES(@taskid,@tempDate,@endDate,@averagework);
      END
   ELSE
      BEGIN
         insert into #OutputTABLE values(@taskid,@tempDate,DATEADD(dy, DATEDIFF(dy,0,@tempDate)+1, -1),@averagework);
      END
   SET @tempDate=DATEADD(dy,@incr+1,DATEADD(dy,DATEDIFF(dy,0,@startDate),0))

   SET @cnt=@cnt-1
   SET @incr=@incr+1

   END

I thought about implementing the solution using a cursor from this, but I'm unsure on how to do it? I am also worried by the comments about looping through rows is bad for performance, so any advice on that is much appreciated!

The table is about 15.000 rows and the average date range is about 31 days, so the view will be circa 465.000 rows. Not a high number, but the table is continually growing so I might also need to limit the view to only the last two years.

  • 1
    This is pretty clear, but I think it would be very helpful to include some sample data and your desired results. I think the idea of `iterating` through your table is wrong though. There are better ways of achieving what I think you are after by using a calendar table and joining. [The answer here with the integer table is close](https://stackoverflow.com/questions/9725349/mysql-expand-date-range-into-new-rows) but a [date table would be better in this case, I believe](https://stackoverflow.com/questions/2157282/generate-days-from-date-range) – JNevill Sep 17 '18 at 13:25

1 Answers1

1

Your question is not very clear, but my magic cystall ball tells me, that you might be looking for this:

SET DATEFORMAT ymd;
CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
);

INSERT INTO #InputTABLE VALUES('six days','2018-06-06','2018-06-12',200)
                             ,('one day','2018-06-06','2018-06-07',200);

SELECT TaskID
      ,DATEADD(DAY,B.Numbr,startdate) AS ExplodingDate
      ,CAST(TaskWork AS DECIMAL(10,4))/A.DayDiff
FROM #InputTABLE
CROSS APPLY(SELECT DATEDIFF(DAY,startdate,enddate) AS DayDiff) A
CROSS APPLY(SELECT TOP (A.DayDiff) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS Numbr FROM master..spt_values) B

DROP TABLE #InputTABLE;

the result

TaskID      ExplodingDate           (Kein Spaltenname)
six days    2018-06-06 00:00:00.000 33.333333333333333
six days    2018-06-07 00:00:00.000 33.333333333333333
six days    2018-06-08 00:00:00.000 33.333333333333333
six days    2018-06-09 00:00:00.000 33.333333333333333
six days    2018-06-10 00:00:00.000 33.333333333333333
six days    2018-06-11 00:00:00.000 33.333333333333333
one day     2018-06-06 00:00:00.000 200.000000000000000

Short explanation
The first APPLY computes the difference in days between your two dates.
The second APPLY uses a trick with TOP and ROW_NUMBER to create a tally table on the fly.
This will create as many rows per input row, as there are days between start and end date.
The rest is simple computation...

UPDATE A complete example with a persistant table

CREATE DATABASE TestDB;
GO
USE TestDB;
GO
CREATE TABLE dbo.RunningNumbers(Number INT NOT NULL
                               ,CalendarDate DATE NOT NULL
                               ,CalendarYear INT NOT NULL
                               ,CalendarMonth INT NOT NULL
                               ,CalendarDay INT NOT NULL
                               ,CalendarWeek INT NOT NULL
                               ,CalendarYearDay INT NOT NULL
                               ,CalendarWeekDay INT NOT NULL);

DECLARE @CountEntries INT = 100000;
DECLARE @StartNumber INT = 0;


WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
    E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
    E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
    E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
    CteTally AS
    (
        SELECT TOP(ISNULL(@CountEntries,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(@StartNumber,0) As Nmbr
        FROM E8
    )
INSERT INTO dbo.RunningNumbers
SELECT CteTally.Nmbr,CalendarDate.d,CalendarExt.*
FROM CteTally
CROSS APPLY
(
    SELECT DATEADD(DAY,CteTally.Nmbr,{ts'1900-01-01 00:00:00'})
) AS CalendarDate(d)
CROSS APPLY
(
    SELECT YEAR(CalendarDate.d) AS CalendarYear
          ,MONTH(CalendarDate.d) AS CalendarMonth
          ,DAY(CalendarDate.d) AS CalendarDay
          ,DATEPART(WEEK,CalendarDate.d) AS CalendarWeek
          ,DATEPART(DAYOFYEAR,CalendarDate.d) AS CalendarYearDay
          ,DATEPART(WEEKDAY,CalendarDate.d) AS CalendarWeekDay
) AS CalendarExt;
GO

SET DATEFORMAT ymd;
CREATE TABLE #InputTABLE
(
TaskID varchar (200),
startdate DATETIME,
enddate DATETIME,
TaskWork int 
);

INSERT INTO #InputTABLE VALUES('six days','2018-06-06','2018-06-12',200)
                             ,('one day','2018-06-06','2018-06-07',200);

SELECT TaskID
      ,B.CalendarDate
      ,CAST(TaskWork AS DECIMAL(10,4))/(A.DayDiff+1)
FROM #InputTABLE
CROSS APPLY(SELECT DATEDIFF(DAY,startdate,enddate) AS DayDiff) A
CROSS APPLY(SELECT * FROM dbo.RunningNumbers WHERE CalendarDate BETWEEN startdate AND enddate) B

DROP TABLE #InputTABLE;
GO

USE master;
GO

DROP DATABASE TestDB;
Community
  • 1
  • 1
Shnugo
  • 66,100
  • 9
  • 53
  • 114
  • Might be correct, but what does the FROM master..spt_values do? When I try to run it, I get this error message: "Reference to database and/or server name in 'master..spt_values' is not supported in this version of SQL Server." – user2776167 Sep 18 '18 at 12:35
  • @user2776167 `master..spt_values` is just a table existing in a normal SQL-Server environment with some thousand rows... We do not need the rows, just any table, which has enough rows (at least your max distance between start an end date. Such a table can easily be created on the fly. You can use any existing table with a lot of rows. Best would be a persistant *numbers/date table*. [You might have a look here](https://stackoverflow.com/questions/32474236/construct-date-from-year-and-week-number-in-mssql/32474751#32474751) – Shnugo Sep 18 '18 at 12:41
  • I have added the RunningNumbers table and when calling that one it appears to work. I still get this error: "A TOP or FETCH clause contains an invalid value." I checked and the max date difference is 12.870 and the input tabel is about 16.000 rows. So what am I missing? – user2776167 Sep 19 '18 at 07:03
  • @user2776167 with the running number table you don't need this at all. Just `cross apply` the needed rows by date... – Shnugo Sep 19 '18 at 07:06
  • I solved it by adding a `WHERE A.DayDiff IS NOT NULL` clause at the end. I found out that I had `NULL` values in the start and end dates. Many thanks for the help, you are awesome! – user2776167 Sep 19 '18 at 08:58
  • @user2776167 Glad that you found a solution... I just added an update to my answer with a complete example with a numbers table. Might be interesting... – Shnugo Sep 19 '18 at 09:47