我有一个包含时钟设置条目表的数据库。这可以作为员工日可能陷入的“时间段”。该表可以包含任意数量的桶,只要它们不相互重叠即可。
例如,这是我们的桶表:
ID Start End timeType
-------------------------------------------------
1 08:00:00.000 12:00:00.000 REGULAR
1 12:00:00.000 12:30:00.000 BREAK
1 12:30:00.000 16:00:00.000 REGULAR
1 16:00:00.000 00:00:00.000 OVERTIME
我有一个时间说,07:55和17:00的打卡时间。我需要弄清楚每天有多少时间落入每个桶中,以小时,分钟和秒为单位。数据输出必须如下所示,我无法在任何一个表中添加列:
ID Start End timeType hrs
-----------------------------------------------------
1 07:55:00.000 12:00:00.000 REGULAR 4.08
1 12:00:00.000 12:30:00.000 BREAK 0.50
1 12:30:00.000 16:00:00.000 REGULAR 3.50
1 16:00:00.000 00:00:00.000 OVERTIME 1.00
我正在考虑一个SQL内联表值函数,它将一次运行一天,但我无法进入小时计算部分。到目前为止,我认为我有所有场景的逻辑,我只需要帮助计算每个场景的小时数(5,2)。我把它放在那里用于SQL建议但是......我是不是太复杂了?
这是我对每个场景的逻辑的准备:
Select Case When CONVERT(time, @PunchInDate) <= CONVERT(time, EndDate)
And CONVERT(time, @PunchInDate) >= CONVERT(time, StartDate)
AND CONVERT(time, @PunchOutDate) <= CONVERT(time, EndDate)
AND CONVERT(time, @PunchOutDate) >= CONVERT(time, StartDate)Then
'Starts and ends in this range.'
Else
''
End as ScenarioA
, Case
When CONVERT(time, @PunchInDate) <= CONVERT(time, StartDate)
AND CONVERT(time, @PunchInDate) <= CONVERT(time, EndDate)
AND CONVERT(time, @PunchOutDate) >= CONVERT(time, StartDate)
AND CONVERT(time, @PunchOutDate) <= CONVERT(time, EndDate)
Then
'Starts before this range and ends in this range'
Else
''
End as ScenarioB
, Case
When CONVERT(time, @PunchInDate) >= CONVERT(time, StartDate)
And CONVERT(time, @PunchInDate) <= CONVERT(time, EndDate)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, StartDate)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, EndDate)Then
'Starts in this range and ends after the range'
Else ''
END as ScenarioC
, Case
When CONVERT(time, @PunchInDate) <= CONVERT(time, StartDate)
And CONVERT(time, @PunchInDate) >= CONVERT(time, EndDate)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, StartDate)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, EndDate)Then
'Starts before this range and ends after the range'
Else ''
END as ScenarioD
From MyTable
Where EmpID = @EmpID
答案 0 :(得分:0)
您可能应将存储桶Start
和End
值存储为int
值,而不是时间或其他值。
要将datetime
值或表达式转换为可用于匹配存储桶的内容,您可以使用此代码中的内容:
DECLARE @when datetime = GETDATE();
SELECT DATEDIFF(minute, DATEADD( day, DATEDIFF(day, 0, @when), 0 ), @when);
--MinutesSinceStartOfDate
-------------------------
--858
--(1 row(s) affected)
如果你需要秒,只需将上面改为:
DECLARE @when datetime = GETDATE();
SELECT DATEDIFF(second, DATEADD( day, DATEDIFF(day, 0, @when), 0 ), @when);
--SecondsSinceStartOfDate
-------------------------
--52466
--(1 row(s) affected)
以下是一些您应该能够适应“桶”的代码:
CREATE TABLE #buckets (
Id int,
Start int,
Finish int
);
GO
INSERT #buckets ( Id, Start, Finish )
VALUES ( 1, 8, 12 ),
( 2, 12, 13 ),
( 3, 13, 16 ),
( 4, 16, 24 );
DECLARE @beginning int = 9,
@ending int = 17;
SELECT x.*,
EffectiveInterval = x.EffectiveFinish - x.EffectiveStart
FROM ( SELECT *,
EffectiveStart = CASE WHEN Start < @beginning THEN @beginning ELSE Start END,
EffectiveFinish = CASE WHEN Finish > @ending THEN @ending ELSE Finish END
FROM #buckets
WHERE Finish >= @beginning
AND Start <= @ending
) x;
DROP TABLE #buckets;
--(4 row(s) affected)
--Id Start Finish EffectiveStart EffectiveFinish EffectiveInterval
------------- ----------- ----------- -------------- --------------- -----------------
--1 8 12 9 12 3
--2 12 13 12 13 1
--3 13 16 13 16 3
--4 16 24 16 17 1
--(4 row(s) affected)
CREATE TABLE #buckets (
Id int,
Start time,
Finish time
);
INSERT #buckets ( Id, Start, Finish )
VALUES ( 1, '08:00', '12:00' ),
( 2, '12:00', '12:30' ),
( 3, '12:30', '16:00' ),
( 4, '16:00', '11:59:59.999' );
DECLARE @PunchInDate datetime = '2014-05-15 07:55',
@PunchOutDate datetime = '2014-05-15 17:00';
Select Case When CONVERT(time, @PunchInDate) <= CONVERT(time, Finish)
And CONVERT(time, @PunchInDate) >= CONVERT(time, Start)
AND CONVERT(time, @PunchOutDate) <= CONVERT(time, Finish)
AND CONVERT(time, @PunchOutDate) >= CONVERT(time, Start)Then
'Starts and ends in this range.'
Else
''
End as ScenarioA
, Case
When CONVERT(time, @PunchInDate) <= CONVERT(time, Start)
AND CONVERT(time, @PunchInDate) <= CONVERT(time, Finish)
AND CONVERT(time, @PunchOutDate) >= CONVERT(time, Start)
AND CONVERT(time, @PunchOutDate) <= CONVERT(time, Finish)
Then
'Starts before this range and ends in this range'
Else
''
End as ScenarioB
, Case
When CONVERT(time, @PunchInDate) >= CONVERT(time, Start)
And CONVERT(time, @PunchInDate) <= CONVERT(time, Finish)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, Start)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, Finish)Then
'Starts in this range and ends after the range'
Else ''
END as ScenarioC
, Case
When CONVERT(time, @PunchInDate) <= CONVERT(time, Start)
And CONVERT(time, @PunchInDate) >= CONVERT(time, Finish)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, Start)
And CONVERT(time, @PunchOutDate) >= CONVERT(time, Finish)Then
'Starts before this range and ends after the range'
Else ''
END as ScenarioD
FROM #buckets;
DECLARE @PunchInTime time,
@PunchOutTime time;
SELECT @PunchInTime = CONVERT(time, @PunchInDate),
@PunchOutTime = CONVERT(time, @PunchOutDate);
SELECT x.*,
EffectiveInterval = DATEDIFF(second, x.EffectiveStart, x.EffectiveFinish),
EffectiveIntervalDecimalHourse = DATEDIFF(second, x.EffectiveStart, x.EffectiveFinish) / 3600.
FROM ( SELECT *,
EffectiveStart = CASE WHEN Start < @PunchInTime THEN @PunchInTime ELSE Start END,
EffectiveFinish = CASE WHEN Finish > @PunchOutTime THEN @PunchOutTime ELSE Finish END
FROM #buckets
WHERE Finish >= @PunchInTime
AND Start <= @PunchOutTime
) x;
DROP TABLE #buckets;
--(4 row(s) affected)
--ScenarioA ScenarioB ScenarioC ScenarioD
-------------------------------- ----------------------------------------------- --------------------------------------------- -------------------------------------------------
--(4 row(s) affected)
--Id Start Finish EffectiveStart EffectiveFinish EffectiveInterval EffectiveIntervalDecimalHourse
------------- ---------------- ---------------- ---------------- ---------------- ----------------- ---------------------------------------
--1 08:00:00.0000000 12:00:00.0000000 08:00:00.0000000 12:00:00.0000000 14400 4.000000
--2 12:00:00.0000000 12:30:00.0000000 12:00:00.0000000 12:30:00.0000000 1800 0.500000
--3 12:30:00.0000000 16:00:00.0000000 12:30:00.0000000 16:00:00.0000000 12600 3.500000
--4 16:00:00.0000000 11:59:59.9990000 16:00:00.0000000 11:59:59.9990000 -14401 -4.000277
--(4 row(s) affected)
答案 1 :(得分:0)
如果您还没有,请创建一个数字表。这只是一个包含单个int列的表,其中包含0和某个大数字之间的所有整数(我的表转到9999)。
create table #numbers(num int)
insert #numbers
SELECT TOP 10000 row_number() over(order by t1.number) -1 as N
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
create table #bucket(Id int, StartTime time, EndTime time, TimeType varchar(10))
insert #bucket
select 0, '00:00:00', '08:00:00', 'OTHER' union
select 1, '08:00:00', '12:00:00', 'REGULAR' union
select 2, '12:00:00', '12:30:00', 'BREAK' union
select 3, '12:30:00', '16:00:00', 'REGULAR' union
select 4, '16:00:00', '00:00:00', 'OVERTIME'
declare @punchInDate datetime
declare @punchOutDate datetime
set @punchInDate = '5/15/2014 7:55'
set @punchOutDate = '5/15/2014 17:00'
--Using the number table, break the punchIn and punchOut times into individual rows for each minute and store them in a temp table.
select convert(time, dateadd(mi, n.num, @punchInDate)) TimeMinute
into #temp
from #numbers n
where n.num <= datediff(mi, @punchInDate, @punchOutDate)
order by 1
--Now you can just join your temp rows with your bucket table, grouping and getting the count of the number of minutes in each bucket.
select b.Id, b.StartTime, b.EndTime, b.TimeType, convert(decimal, COUNT(t.TimeMinute))/60
from #bucket b
join #temp t on t.TimeMinute>= b.StartTime and t.TimeMinute <= dateadd(mi, -1, b.EndTime)
group by b.Id, b.StartTime, b.EndTime, b.TimeType
答案 2 :(得分:0)
如果您正在使用SQLServer 2012,则可以通过一次选择获得预期结果。
declare @punchInDate datetime = '5/15/2014 7:55'
declare @punchOutDate datetime = '5/15/2014 17:00';
WITH punchTime AS (
SELECT punchIn = cast(@punchInDate AS Time)
, punchOut = cast(@punchOutDate AS Time)
), T AS (
SELECT b.ID
, b.StartTime, b.EndTime
, pt.punchIn, pt.punchOut
, sIn = SUM(CASE WHEN pt.punchIn < b.StartTime
THEN 1
ELSE 0
END) OVER (ORDER BY ID)
, sOut = SUM(CASE WHEN pt.punchOut > b.StartTime
THEN 1
ELSE 0
END) OVER (ORDER BY ID DESC)
FROM bucket b
CROSS JOIN punchTime pt
), C AS (
SELECT ID
, StartTime = CASE sIn WHEN 1 THEN punchIn ELSE StartTime END
, EndTime = CASE sOut WHEN 1 THEN punchOut ELSE EndTime END
FROM T
)
SELECT ID
, StartTime
, EndTime
, hrs = Cast(DateDiff(mi, StartTime, EndTime) / 60.0 AS Decimal(4, 2))
FROM C
ORDER BY ID
SQLFiddle demo(在演示中,打卡时间在表格中)
使用SUM OVER(ORDER BY)
我们得到一笔滚动金额
sIn
在StartTime位于punchIn之后的第一行中为1
在{StarterHat}之前,StartTime位于punchOut之前的最后一行中,sOut
将为1
使用这些指针很容易将打卡时间替换为标准铲斗时间并获得工作时间。