SQL - 获取多个范围之间的时间总和
SQL - Getting the sum of times between multiple ranges
上图代表了我一直试图在 MSSQL 中完成的事情。
多时间段的排班,A到B是加班,B到C上午正常上班,C到D午饭时间,D到E下午正常上班,E到F也是加班。
为了简单起见,我手动将A、B、C、D、E和F声明为固定值。实际上,这些将由另一个 table 提供,但这与此问题无关。
垂直箭头,代表计时in/out时间。
那么,假设早上 8 点到晚上 7 点。
如何检索以下内容?
工作:8小时
午餐:1小时
超时:2h
无需使用乱七八糟的 IF,并且知道 IN 可能位于 B 和 C 的中间。
上午 11 点上班,晚上 7 点下班的人现在工作 = 6 小时,午餐 = 1 小时,加班 = 1 小时。
谢谢。
举个例子,我目前的意大利面条代码,肯定有更优雅(更简洁)的方法来完成这个。
declare @clockIn time = '08:00'
declare @clockOut time = '19:00'
declare @a time= '00:00'
...all possible limits defined here...
declare @f time= '23:59'
tests using C to D only:
-- in block
IF(@clockIn >= @c
AND @clockOut <= @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @clockIn, @clockOut));
END;
-- outside block
IF(@clockIn < @c
AND @clockOut > @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @c, @d));
END;
-- cIn in block, cOut outside
IF(@clockIn >= @c
AND @clockOut > @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @clockIn, @d));
END;
-- cIn outside, cIn inside
IF(@clockIn < @c
AND @clockOut <= @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @c, @clockOut));
END
select @ax as overtime,@at as work,@ai as lunch
如果不以某种方式构建 if-then 逻辑,我想不出有什么方法可以做到这一点,所以这取决于你如何去做。我认为最简单(也可能是最易读)的方法是每次需要时都使用一个 case 表达式,也许留下一些关于它背后的逻辑的评论,这样任何遇到它的人都可以更容易地理解它是如何工作的。
例如,像这样的东西应该足够简单而不至于太乱(IMO):
DECLARE @Clock TABLE (Clockin TIME NOT NULL, Clockout TIME NOT NULL);
INSERT @Clock VALUES ('12:30', '19:00');
DECLARE @Times TABLE (WorkStart TIME NOT NULL, LunchStart TIME NOT NULL, LunchEnd TIME NOT NULL, WorkEnd TIME NOT NULL);
INSERT @Times VALUES ('09:00', '12:00', '13:00', '18:00');
SELECT
OverTime =
(CASE -- Overtime hours in the morning.
WHEN C.Clockin < T.WorkStart -- Clocked in before official work start.
THEN DATEDIFF(MINUTE, C.Clockin, CASE WHEN C.Clockout > T.WorkStart THEN T.WorkStart ELSE C.Clockout END)
ELSE 0
END +
CASE -- Overtime hours in the evening.
WHEN C.Clockout > T.WorkEnd -- Clocked out after official work end.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin > T.WorkEnd THEN C.Clockin ELSE T.WorkEnd END, C.Clockout)
ELSE 0
END) / 60.0,
Work =
(CASE -- Work hours for the morning.
WHEN C.Clockout > T.WorkStart AND C.Clockin < T.LunchStart -- Clocked out after official work start and clocked in before official lunch start.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin < T.WorkStart THEN T.WorkStart ELSE C.Clockin END, CASE WHEN C.Clockout < T.LunchStart THEN C.Clockout ELSE T.LunchStart END)
ELSE 0
END +
CASE -- Work hours for the afternoon.
WHEN C.Clockin < T.WorkEnd AND C.Clockout > T.LunchEnd -- Clocked out after official lunch end and clocked in before official work end.
THEN DATEDIFF(MINUTE,CASE WHEN C.Clockin < T.LunchEnd THEN T.LunchEnd ELSE C.Clockin END, CASE WHEN C.Clockout < T.WorkEnd THEN C.Clockout ELSE T.WorkEnd END)
ELSE 0
END) / 60.0,
Lunch =
CASE -- Lunch hours.
WHEN C.Clockin < T.LunchEnd AND C.Clockout > T.LunchStart -- Clocked in before official lunch end and clocked out after official lunch start.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin > T.LunchStart THEN C.Clockin ELSE T.LunchStart END, CASE WHEN C.Clockout < T.LunchEnd THEN C.Clockout ELSE T.LunchEnd END)
ELSE 0
END / 60.0
FROM @Clock AS C
CROSS JOIN @Times AS T;
上图代表了我一直试图在 MSSQL 中完成的事情。
多时间段的排班,A到B是加班,B到C上午正常上班,C到D午饭时间,D到E下午正常上班,E到F也是加班。
为了简单起见,我手动将A、B、C、D、E和F声明为固定值。实际上,这些将由另一个 table 提供,但这与此问题无关。
垂直箭头,代表计时in/out时间。
那么,假设早上 8 点到晚上 7 点。
如何检索以下内容?
工作:8小时
午餐:1小时
超时:2h
无需使用乱七八糟的 IF,并且知道 IN 可能位于 B 和 C 的中间。
上午 11 点上班,晚上 7 点下班的人现在工作 = 6 小时,午餐 = 1 小时,加班 = 1 小时。
谢谢。
举个例子,我目前的意大利面条代码,肯定有更优雅(更简洁)的方法来完成这个。
declare @clockIn time = '08:00'
declare @clockOut time = '19:00'
declare @a time= '00:00'
...all possible limits defined here...
declare @f time= '23:59'
tests using C to D only:
-- in block
IF(@clockIn >= @c
AND @clockOut <= @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @clockIn, @clockOut));
END;
-- outside block
IF(@clockIn < @c
AND @clockOut > @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @c, @d));
END;
-- cIn in block, cOut outside
IF(@clockIn >= @c
AND @clockOut > @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @clockIn, @d));
END;
-- cIn outside, cIn inside
IF(@clockIn < @c
AND @clockOut <= @d)
BEGIN
SET @at = @at + ABS(DATEDIFF(hour, @c, @clockOut));
END
select @ax as overtime,@at as work,@ai as lunch
如果不以某种方式构建 if-then 逻辑,我想不出有什么方法可以做到这一点,所以这取决于你如何去做。我认为最简单(也可能是最易读)的方法是每次需要时都使用一个 case 表达式,也许留下一些关于它背后的逻辑的评论,这样任何遇到它的人都可以更容易地理解它是如何工作的。
例如,像这样的东西应该足够简单而不至于太乱(IMO):
DECLARE @Clock TABLE (Clockin TIME NOT NULL, Clockout TIME NOT NULL);
INSERT @Clock VALUES ('12:30', '19:00');
DECLARE @Times TABLE (WorkStart TIME NOT NULL, LunchStart TIME NOT NULL, LunchEnd TIME NOT NULL, WorkEnd TIME NOT NULL);
INSERT @Times VALUES ('09:00', '12:00', '13:00', '18:00');
SELECT
OverTime =
(CASE -- Overtime hours in the morning.
WHEN C.Clockin < T.WorkStart -- Clocked in before official work start.
THEN DATEDIFF(MINUTE, C.Clockin, CASE WHEN C.Clockout > T.WorkStart THEN T.WorkStart ELSE C.Clockout END)
ELSE 0
END +
CASE -- Overtime hours in the evening.
WHEN C.Clockout > T.WorkEnd -- Clocked out after official work end.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin > T.WorkEnd THEN C.Clockin ELSE T.WorkEnd END, C.Clockout)
ELSE 0
END) / 60.0,
Work =
(CASE -- Work hours for the morning.
WHEN C.Clockout > T.WorkStart AND C.Clockin < T.LunchStart -- Clocked out after official work start and clocked in before official lunch start.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin < T.WorkStart THEN T.WorkStart ELSE C.Clockin END, CASE WHEN C.Clockout < T.LunchStart THEN C.Clockout ELSE T.LunchStart END)
ELSE 0
END +
CASE -- Work hours for the afternoon.
WHEN C.Clockin < T.WorkEnd AND C.Clockout > T.LunchEnd -- Clocked out after official lunch end and clocked in before official work end.
THEN DATEDIFF(MINUTE,CASE WHEN C.Clockin < T.LunchEnd THEN T.LunchEnd ELSE C.Clockin END, CASE WHEN C.Clockout < T.WorkEnd THEN C.Clockout ELSE T.WorkEnd END)
ELSE 0
END) / 60.0,
Lunch =
CASE -- Lunch hours.
WHEN C.Clockin < T.LunchEnd AND C.Clockout > T.LunchStart -- Clocked in before official lunch end and clocked out after official lunch start.
THEN DATEDIFF(MINUTE, CASE WHEN C.Clockin > T.LunchStart THEN C.Clockin ELSE T.LunchStart END, CASE WHEN C.Clockout < T.LunchEnd THEN C.Clockout ELSE T.LunchEnd END)
ELSE 0
END / 60.0
FROM @Clock AS C
CROSS JOIN @Times AS T;