我对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')
答案 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])