拆分超时记录,该记录介于夜班之间

时间:2018-08-18 22:25:02

标签: sql-server ssms

我对SQL Server还是很陌生,我正在尝试将超时记录拆分为两次,这些记录介于雇员班次表的夜班之间,但是这样做很困难。夜班时间为晚上10点至早上7点

下面的示例可能有助于使事情更清楚。

例如

EmployeeId   InTime                     OutTime                  
----------   -----------------------    ----------------------- 
1            2018-08-10 21:00:00.000   2018-08-11 09:00:00.000  

所需的输出

EmployeeId   InTime                     OutTime                   Type
----------  -----------------------    -----------------------   -------
1            2018-08-10 21:00:00.000    2018-08-10 22:00:00.000   day
1            2018-08-10 22:00:00.000    2018-08-11 07:00:00.000   night
1            2018-08-11 07:00:00.000    2018-08-11 09:00:00.000   day

当前表的代码:

DECLARE @tbl TABLE 
    (
        EmployeeId INT,
        InTime DATETIME,
        OutTime DATETIME
    )

INSERT INTO @tbl (Employee_Id, Shift_Start, Shift_end) 
VALUES (1, '2018-08-10 21:00:00.000', '2018-08-11 09:00:00.000')

3 个答案:

答案 0 :(得分:0)

我为您提供了一个示例,它可以满足您的要求,但是效率很低。

它实质上使用了一个循环并会输出您想要的内容,但是如果您打算大规模使用它,我建议您研究一下使它更有效的方法。

我在下面添加了一些注释,供您阅读代码中的内容。

--DROP TABLE #data

SELECT 
    '1' as [Employee ID], 
    '2018-08-10 21:00:00.000' as [InTime], 
    '2018-08-11 09:00:00.000' as [OutTime]
INTO #data


DECLARE @MinDate datetime --Create variables used for the loop
DECLARE @MaxDate datetime
DECLARE @CurDate datetime
DECLARE @EmployeeID int

--Set the variables based off what is in the #data table
SELECT @MinDate = InTime,
       @MaxDate = Outtime,
       @EmployeeID = [Employee ID]
FROM #data

SET @CurDate = @MinDate

--DROP TABLE #datalines
--Create a table to insert lines into
CREATE TABLE #datalines
(   
    EmployeeID int,
    TheDate datetime,
    ShiftType NVARCHAR(10),
    ShiftTypeNo int
)

DECLARE @ShiftType NVARCHAR(10) = ''
DECLARE @ShiftTypeNumber int = 0 --This is used for splitting out shifts betwen day & night for grouping later on

WHILE(dateadd(mi, 1, @CurDate) <= @maxdate) --While there is still minutes unaccounted for, loop through every minute between start & end
BEGIN
    IF(DATEPART(hour, @curDate) IN (22,23,0,1,2,3,4,5,6) AND @ShiftType != 'Night') --If we currently have not set the shift type, or it changes and is incorrect, then set it.
    BEGIN
        SET @ShiftTypeNumber = @ShiftTypeNumber + 1
        SET @ShiftType = 'Night' --If we are in the night hours, then set the shift type to night.
    END
    ELSE IF(DATEPART(hour, @curDate) NOT IN (22,23,0,1,2,3,4,5,6) AND @ShiftType != 'Day')
    BEGIN
        SET @ShiftTypeNumber = @ShiftTypeNumber + 1
        SET @ShiftType = 'Day' --If we are in the day hours, then set the shift type to day.
    END

    --Insert into our data table with the current date in minutes and what shift type and shift number it is
    INSERT INTO #datalines 
    SELECT @EmployeeID,@CurDate, @ShiftType, @ShiftTypeNumber

    --Add a minute to our date loop.
    SET @CurDate = dateadd(mi, 1, @CurDate) 
END

--Output the results
SELECT [EmployeeID],
       MIN([TheDate]) as [Start Shift],
       CASE 
            WHEN MAX([TheDate]) < @MaxDate THEN DATEADD(mi,1, MAX([TheDate])) ELSE MAX([TheDate]) END
        as [End Shift], --If it isn't the end of the shift, add 1 minute (otherwise it would show the hour before with 59 mins)
       [ShiftType]
FROM #datalines
GROUP BY [EmployeeID], 
         [ShiftType],
        [ShiftTypeNo]
ORDER BY [Start Shift]

答案 1 :(得分:0)

生成所有班次的列表,并将其加入员工工时列表。 注意,这不限于小时或分钟。您甚至可以保留毫秒。

create table EmpHours ( EmployeeID int, InTime datetime, OutTime datetime )

insert into EmpHours ( EmployeeID, InTime, OutTime ) values
( 1, '2018-08-10T21:00:00.000', '2018-08-11T09:00:00.000' ),
( 2, '2000-08-28T17:18:19.123', '2000-08-29T11:12:13.456' ),
( 2, '2000-12-31T16:00:00.555', '2001-01-01T12:44:55.444' ),
( 3, '2018-08-01T18:00:01.123', '2018-08-04T09:09:09.009' ),
( 4, '2018-08-10T09:00:00.000', '2018-08-10T17:00:00.000' ),
( 4, '2018-08-12T23:00:00.023', '2018-08-13T03:04:05.000' ),
( 5, '2018-08-10T17:00:00.017', '2018-08-10T23:08:08.008' ),
( 6, '2018-08-10T03:00:00.703', '2018-08-10T04:04:04.704' )

;
with 
-- Determine the earliest and latest dates.
-- Cast to date to remove the time portion.
-- Cast results back to datetime because we're going to add hours later.
MinMaxDates 
as 
(select cast(min(cast(InTime  as date))as datetime) as MinDate, 
        cast(max(cast(OutTime as date))as datetime) as MaxDate from EmpHours),

-- How many days have passed during that period
Dur
as
(select datediff(day,MinDate,MaxDate) as Duration from MinMaxDates),

-- Create a list of numbers.
-- These will be added to MinDate to get a list of dates.
-- We add a margin to deal with edge cases
NumList
as
( select -2 as Num
  union all
  select Num+1 from NumList,Dur where Num<=Duration+2 ),

-- Create a list of dates by adding those numbers to MinDate
DayList 
as
( select dateadd(day,Num,MinDate)as WorkDate from NumList, MinMaxDates  ),

-- Create a list of shifts starting on those dates
ShiftList
as
( select dateadd(hour, 7,WorkDate) as StartTime, -- from 7 AM to 10 PM
         dateadd(hour,22,WorkDate) as EndTime,
         'day' as [Type]
         from DayList
  union
  select dateadd(hour,22,WorkDate) as StartTime, -- from 10 PM to 7 AM of the next day
         dateadd(hour,31,WorkDate) as EndTime,
         'night' as [Type]
         from DayList  ),

-- Join the list of Shifts to the list of Employee Hours
EmpShiftList
as
( select * from ShiftList, EmpHours 
where InTime<=EndTime and OutTime>=StartTime
),

-- Keep the later   of the shift start time, and the employee in-time
-- Keep the earlier of the shift   end time, and the employee out-time
EmpShifts
as
( select EmployeeID, 
         case when InTime>=StartTime then InTime  else StartTime end as InTime,
         case when OutTime<=EndTime  then OutTime else EndTime   end as OutTime,
         [Type] from EmpShiftList)

-- List the results in order
-- Use MaxRecursion option in case there are more than 100 days 
select * from EmpShifts 
order by EmployeeID, InTime, OutTime
option (maxrecursion 0)

这是另一种不使用数字列表或递归的方式。

注意:进一步考虑,以下代码并不总是给出与上面代码相​​同的答案。如果从InTime到OutTime的时间超过几天,它将失败。这可能是不太可能的情况,但我认为这是一个错误。尝试使用此输入:

insert into EmpHours ( EmployeeID, InTime, OutTime ) values
( 7777, '2018-08-10T21:22:23.245', '2018-08-13T09:03:04.056' )

出于历史目的,我将其保留在此处,但我建议使用第一个版本。

; -- *** DO NOT USE THIS VERSION *** See notes above. 
with 
-- Get list of dates from table
-- Cast to date to remove time portion
DayListA 
as
( select cast(InTime as date) as DateA from EmpHours
  union
  select cast(OutTime as date) as DateA from EmpHours
),
-- For each such date, add the preceding date.
-- This is to deal with shifts that start the previous day
DayListB
as
( select DateA as DateB from DayListA
  union
  select DateAdd(day,-1,DateA) as DateB from DayListA
),
-- Cast to datetime so we can add hours
-- Remove duplicates
DayList
as
( select distinct cast(DateB as datetime) as WorkDate from DayListB
),
-- Create a list of shifts starting on those dates
ShiftList
as
( select dateadd(hour, 7,WorkDate) as StartTime, -- from 7 AM to 10 PM
         dateadd(hour,22,WorkDate) as EndTime,
         'day' as [Type]
         from DayList
  union
  select dateadd(hour,22,WorkDate) as StartTime, -- from 10 PM to 7 AM of the next day
         dateadd(hour,31,WorkDate) as EndTime,
         'night' as [Type]
         from DayList  ),

-- Join the list of Shifts to the list of Employee Hours
EmpShiftList
as
( select * from ShiftList, EmpHours 
where InTime<=EndTime and OutTime>=StartTime
),

-- Keep the later   of the shift start time, and the employee in-time
-- Keep the earlier of the shift   end time, and the employee out-time
EmpShifts
as
( select EmployeeID, 
         case when InTime>=StartTime then InTime else StartTime end as InTime,
         case when OutTime<=EndTime then OutTime else EndTime end as OutTime,
         [Type] from EmpShiftList)

-- List the results in order
select * from EmpShifts 
order by EmployeeID, InTime, OutTime

答案 2 :(得分:0)

尝试使用递归公用表表达式(CTE)来生成最小和最大日期/时间之间的小时列表。然后,您可以使用DENSE_RANK对记录进行分组。

示例:

WITH
source_data
AS
(
    SELECT tbl.* FROM (VALUES
      ( 1, '10-Aug-2018 21:00', '10-Aug-2018 22:00')
    , ( 2, '10-Aug-2018 20:00', '11-Aug-2018 07:00')
    , ( 3, '11-Aug-2018 07:00', '11-Aug-2018 09:00')
    , ( 4, '10-Aug-2018 20:00', '11-Aug-2018 09:00')
    ) tbl ([EmployeeId], [InTime], [OutTime]) 
)
, 
source_data_dates
AS
(
    SELECT [InTimeMin] = MIN([InTime]), [OutTimeMax] = MAX([OutTime]) FROM source_data
)
,
source_data_times([n], [InTime]) 
AS
(
    SELECT 1, CAST([InTimeMin] AS DATETIME) FROM source_data_dates
    UNION ALL
    SELECT n + 1, [InTimeMin] = CAST(DATEADD(hh, n, [InTimeMin]) AS DATETIME)  FROM source_data_times, source_data_dates WHERE n <= DATEDIFF(hh, [InTimeMin], [OutTimeMax])
)
, 
source_data_times_type
AS
(
    SELECT
          [InTime] = [InTime]
        , [OutTime] = DATEADD(hh, 1, [InTime])
        , [Type] = 
            CASE 
                WHEN CAST([InTime] AS TIME) >= '22:00:00' OR CAST([InTime] AS TIME) < '07:00:00'
                THEN 'night'
                ELSE 'day'
            END
    FROM 
        source_data_times
)
, 
source_data_times_type_rank
AS
(
    SELECT 
          sd.[EmployeeId]
        , tt.[InTime]
        , tt.[OutTime]
        , tt.[Type]
        , [GroupRank] = DENSE_RANK() OVER (PARTITION BY sd.[EmployeeId] ORDER BY tt.[Type], CAST(tt.[InTime] AS DATE))
    FROM
        source_data AS sd
        INNER JOIN source_data_times_type AS tt ON tt.[InTime] >= sd.[InTime] AND tt.[InTime] < sd.[OutTime]
)
SELECT
      [EmployeeId]
    , [InTime] = MIN([InTime])
    , [OutTime] = MAX([OutTime])
    , [Type]
FROM 
    source_data_times_type_rank
GROUP BY 
      [EmployeeId]
    , [Type]
    , [GroupRank]
ORDER BY
      [EmployeeId]
    , MIN([InTime])

结果:

screenshot