Hours by Day Parts

Im having the worst time trying to figure this out. I need to get the number of hours and minutes worked per a portion of the day.

CREATE TABLE #DayPart (DayPartID INT, [Name] NVARCHAR(20), StartHour INT, StartMinute INT)

CREATE TABLE #Shifts (EmployeeNumber INT, DateofBusiness DATETIME, InHour INT, InMinute INT, OutHour INT, OutMinute INT, TotalHours DECIMAL(15,2), TOTALMinutes INT)

INSERT INTO #DayPart
VALUES (1,'Day', 8,0), (2,'Evening', 17,0), (3,'Overnight', 23,0)

INSERT INTO #Shifts
VALUES (1000, '01-01-2019', 11, 10, 0, 30, 13.33, 800), (1001, '01-01-2019', 8, 30, 18, 30, 10, 600)

Using EmployeeNumber 1000, I expect this outcome:

image

The confusing part of this for me is that Daypart doesn't have an End time. So, presumably, it ends when the next day part starts. So the last day part 23, presumably ends at 8am, when daypart 1 starts. I tried to use lead() to get the next start hour, But I still cannot seem to get my results. Lead() also presents a problem for the LAST record (ID 3) because there is no record after that.

Any suggestions would be greatly appreciated!!! Im on day 2 of trying to wrap my head around this.

Thanks!!

I broke it up hour by hour just to make it easier to join. Please check this to see if it is close enough to use. Then we can adjust it to handle specific minutes for the first hour and the last hour (all in-between hours will always be a full 60 minutes).

I didn't use LEAD/LAG because I don't like to assume it's available, I forgot you'd explicitly mentioned it in your comments.

;WITH
cte_day_hours AS (
    SELECT CA1.*
    FROM #DayPart DP1
    OUTER APPLY (
        SELECT TOP (1) *
        FROM #DayPart DP2
        WHERE DP2.StartHour > DP1.StartHour
        ORDER BY DP2.StartHour
    ) AS DP2
    OUTER APPLY (
        SELECT TOP (1) *
        FROM #DayPart DP3
        ORDER BY DP3.StartHour
    ) AS DP3
    CROSS APPLY (
        SELECT DP1.DayPartID, DP1.StartHour, ISNULL(DP2.StartHour, 25) - 1 AS EndHour
        UNION ALL
        SELECT DP1.DayPartID, 1, DP3.StartHour - 1
        WHERE DP2.StartHour IS NULL
    ) AS CA1

),
--WITH
cteTally10 AS (
    SELECT * FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS numbers(number)
),
cteTally100 AS (
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 AS number
    FROM cteTally10 c1
    CROSS JOIN cteTally10 c2
)
SELECT s.InHour + hours.number % 24, cdh.DayPartID, s.*
FROM #Shifts s
INNER JOIN cteTally100 hours ON hours.number BETWEEN 0 AND 
    OutHour + CASE WHEN OutHour < Inhour THEN 24 ELSE 0 END - InHour
INNER JOIN cte_day_hours cdh ON s.InHour + hours.number BETWEEN cdh.StartHour AND cdh.EndHour
ORDER BY EmployeeNumber, hours.number

i didnt understood the timings in total hours column such as how you got 11:10-17:00

What I did below is probably overkill to solve your situation, but this one should be pretty universal as it allows you to to have skifts starting after midnight. To do this, I allowed myself to add a new column to the daypart table in order to track in which order the shifts come.
If you don't like this change, you can change the below query, where you change all references to dayshiftorder to ex. daypartid or starthour,startminute.

The daypart table now looks like this:
CREATE TABLE #DayPart (DayPartID INT, [Name] NVARCHAR(20), StartHour INT, StartMinute INT, DayShiftOrder INT)

and your entries should look like this:
INSERT INTO #DayPart
VALUES (1,'Day', 8,0,1), (2,'Evening', 17,0,2), (3,'Overnight', 23,0,3)

Now to the query:

declare @dt_start date=cast('2019-01-01' as date);
declare @dt_end date=cast('2019-01-01' as date);

with cte_daypart
  as (select row_number() over(order by starthour,startminute) as rn
            ,daypartid
            ,[name]
            ,dateadd(minute,starthour*60+startminute,cast('00:00' as time)) as starttime
            ,starthour*60+startminute as mm
        from #daypart
     )
    ,cte_workday
  as (select dt_start
            ,dt_end
        from (select dateadd(minute
                            ,a.mm
                            +(row_number() over(order by (select null))-1)*24*60
                            ,cast(@dt_start as datetime)
                            )
                     as dt_start
                    ,dateadd(minute
                            ,a.mm
                            +(row_number() over(order by (select null)))*24*60
                            ,cast(@dt_start as datetime)
                            )
                     as dt_end
                from cte_daypart as a
                     cross apply (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) as b1(n)
                     cross apply (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) as b2(n)
             ) as a
       where dt_end<=dateadd(day,2,@dt_end)
     )
    ,cte_daypart1
  as (select row_number() over(order by dayshiftorder) as rn
            ,daypartid
            ,[name]
            ,starthour*60+startminute as minstart
        from #daypart
     )
    ,cte_daypart2
  as (select a.rn
            ,a.daypartid
            ,a.[name]
            ,a.minstart+case when a.minstart<c.minstart then 24*60 else 0 end as minstart
            ,isnull(b.minstart+case when b.minstart<c.minstart then 24*60 else 0 end,c.minstart+24*60)
            -(a.minstart+case when a.minstart<c.minstart then 24*60 else 0 end) as minlength
        from cte_daypart1 as a
             left outer join cte_daypart1 as b
                          on b.rn=a.rn+1
             inner join cte_daypart1 as c
                         on c.rn=1
     )
    ,cte_daypart3
  as (select top(1) with ties
             a.daypartid
            ,a.[name]
            ,dateadd(minute
                    ,a.minstart
                    +(row_number() over(partition by a.daypartid
                                            order by (select null)
                                       )
                     -1
                     )*24*60
                    ,cast(@dt_start as datetime)
                    )
             as dt_start
            ,dateadd(minute
                    ,a.minstart
                    +a.minlength
                    +(row_number() over(partition by a.daypartid
                                            order by (select null)
                                       )
                     -1
                     )*24*60
                    ,cast(@dt_start as datetime)
                    )
             as dt_end
        from cte_daypart2 as a
             cross apply (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) as b1(n)
             cross apply (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) as b2(n)
       order by  sign(row_number() over(partition by a.rn
                                            order by (select null)
                                       )
                     -datediff(day,@dt_start,@dt_end)
                     -2
                     )
     )
    ,cte_shifts
  as (select employeenumber
            ,dateadd(minute,inhour*60+inminute,dateofbusiness) as dt_start
            ,dateadd(minute,inhour*60+inminute+totalminutes,dateofbusiness) as dt_end
        from #shifts
       where dateofbusiness>=@dt_start
         and dateofbusiness<dateadd(day,2,@dt_end)
     )
select b.employeenumber
      ,a.daypartid
      ,a.[name]
      ,case when a.dt_start<b.dt_start then b.dt_start else a.dt_start end as dt_start
      ,case when a.dt_end<b.dt_end then a.dt_end else b.dt_end end as dt_end
      ,dateadd(minute
              ,datediff(minute
                       ,case when a.dt_start<b.dt_start then b.dt_start else a.dt_start end
                       ,case when a.dt_end<b.dt_end then a.dt_end else b.dt_end end
                       )
              ,cast('00:00:00' as time)
              )
       as totalhours
      ,datediff(minute
               ,case when a.dt_start<b.dt_start then b.dt_start else a.dt_start end
               ,case when a.dt_end<b.dt_end then a.dt_end else b.dt_end end
               )
       as totalminutes
  from cte_daypart3 as a
       inner join cte_shifts as b
               on b.dt_end>=a.dt_start
              and b.dt_start<=a.dt_end
 order by b.employeenumber
         ,a.dt_start
;