我在数据库中有以下数据:
Job ClockInDateTime ClockOutDateTime
MM00151509 2013-11-19 07:01 2013-11-19 09:20
MM00151800 2013-11-09 09:08 2013-11-20 11:36
MM00153591 2013-12-01 08:20 2013-12-03 08:15
MM00154121 2013-12-05 08:19 2013-12-05 10:32
我想记下每一行并显示该日期以及该日期的相关小时数。正如您在上面的结果中所看到的,许多记录跨越ClockInDateTime和ClockOutDateTime之间的天数。有成千上万的乔布斯(行)。
执行此操作的SQL查询是什么?
上述一个作业的示例输出 - MM00153591:
Job ClockInDateTime ClockOutDateTime Date Hours
MM00153591 2013-12-01 08:20 2013-12-03 08:26 12/1/2013 15.3333
MM00153591 2013-12-01 08:20 2013-12-03 08:26 12/2/2013 24
MM00153591 2013-12-01 08:20 2013-12-03 08:15 12/3/2013 8.25
答案 0 :(得分:1)
这里的诀窍是你想要获得一个行爆炸,导致指定日期之间每天产生一行。
这可以通过几种方式实现,但一种方法是加入一个简单的日期表,其中包含所有可能的日期(在合理范围内),其中连接条件在一个范围内。根据您的性能需求,可以即时构建,也可以提前构建,并存储起来供以后使用。
以下是一个示例,假设您的源3列位于名为Job的表中:
DECLARE @MinDate DATETIME
DECLARE @TotalDays INT
SELECT @MinDate = DATEADD(dd,-1, CONVERT(DATE,MIN(ClockInDateTime))),
@TotalDays = DATEDIFF(dd, CONVERT(DATE,MIN(ClockInDateTime)), CONVERT(DATE,MAX(ClockOutDateTime))) + 2
FROM Job
SELECT J.Job, J.ClockInDateTime, J.ClockOutDateTime,
CONVERT(DATE, Dt) as [Date],
CASE WHEN ClockInDateTime < Dt AND ClockOutDateTime >= DATEADD(dd, 1, Dt) THEN 24*60
WHEN ClockInDateTime >= Dt AND ClockOutDateTime >= DATEADD(dd, 1, Dt)
THEN DATEDIFF(mi, ClockInDateTime, DATEADD(dd, 1, Dt))
WHEN ClockInDateTime < Dt AND ClockOutDateTime < DATEADD(dd, 1, Dt)
THEN DATEDIFF(mi, Dt, ClockOutDateTime)
ELSE DATEDIFF(mi, ClockInDateTime, ClockOutDateTime)
END / 60.0 as Hours
FROM Job J
INNER JOIN (SELECT TOP (@TotalDays) DATEADD(dd, ROW_NUMBER() OVER (ORDER BY s1.object_id), @MinDate) as Dt
FROM sys.objects s1
CROSS JOIN sys.objects s2) as DateTable
ON Dt BETWEEN CONVERT(DATE,J.ClockInDateTime) AND CONVERT(DATE,J.ClockOutDateTime)
ORDER BY Job, [Date]
使用您的样本数据进行的一些简单测试会产生看似正确的结果,但是对于您调出的工作,我确实得到了第一天的总小时数略有不同的答案:
Job ClockInDateTime ClockOutDateTime Date Hours
MM00153591 2013-12-01 08:20 2013-12-03 08:26 12/1/2013 15.6666
MM00153591 2013-12-01 08:20 2013-12-03 08:26 12/2/2013 24
MM00153591 2013-12-01 08:20 2013-12-03 08:15 12/3/2013 8.25
注意:我上面用来构建日期表的方法是生成&#34;数字表&#34;的简单方法,但也有其他可能更可靠的方法:{{ 3}}
编辑:这里是针对提供的示例数据的完整脚本,以及不使用日期表的替代方法,而是针对整个集合中的每一天循环。每种方法的完整结果也不加改变地提供:
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Table') IS NOT NULL DROP TABLE #Table
CREATE TABLE #Table (Job NVARCHAR(256), ClockInDateTime DATETIME, ClockOutDateTime DATETIME)
INSERT INTO #Table (Job, ClockInDateTime, ClockOutDateTime)
SELECT N'MM00151509', '2013-11-19 07:01', '2013-11-19 09:20'
UNION ALL SELECT N'MM00151800','2013-11-09 09:08','2013-11-20 11:36'
UNION ALL SELECT N'MM00153591','2013-12-01 08:20','2013-12-03 08:15'
UNION ALL SELECT N'MM00154121','2013-12-05 08:19','2013-12-05 10:32'
PRINT 'Method 1: Calculate as a subset of all possible days'
DECLARE @MinDate DATETIME
DECLARE @TotalDays INT
SELECT @MinDate = DATEADD(dd,-1, CONVERT(DATE,MIN(ClockInDateTime))),
@TotalDays = DATEDIFF(dd, CONVERT(DATE,MIN(ClockInDateTime)), CONVERT(DATE,MAX(ClockOutDateTime))) + 2
FROM #Table
SELECT J.Job, J.ClockInDateTime, J.ClockOutDateTime,
CONVERT(DATE, Dt) as [Date],
CASE WHEN ClockInDateTime < Dt AND ClockOutDateTime >= DATEADD(dd, 1, Dt) THEN 24*60
WHEN ClockInDateTime >= Dt AND ClockOutDateTime >= DATEADD(dd, 1, Dt)
THEN DATEDIFF(mi, ClockInDateTime, DATEADD(dd, 1, Dt))
WHEN ClockInDateTime < Dt AND ClockOutDateTime < DATEADD(dd, 1, Dt)
THEN DATEDIFF(mi, Dt, ClockOutDateTime)
ELSE DATEDIFF(mi, ClockInDateTime, ClockOutDateTime)
END / 60.0 as Hours
FROM #Table J
INNER JOIN (SELECT TOP (@TotalDays) DATEADD(dd, ROW_NUMBER() OVER (ORDER BY s1.object_id), @MinDate) as Dt
FROM sys.objects s1
CROSS JOIN sys.objects s2) as DateTable
ON Dt BETWEEN CONVERT(DATE,J.ClockInDateTime) AND CONVERT(DATE,J.ClockOutDateTime)
ORDER BY Job, [Date]
PRINT 'Method 2: Loop 1 day at a time'
GO
IF OBJECT_ID('dbo.udf_MinDate') IS NOT NULL DROP FUNCTION dbo.udf_MinDate
GO
IF OBJECT_ID('dbo.udf_MaxDate') IS NOT NULL DROP FUNCTION dbo.udf_MaxDate
GO
CREATE FUNCTION dbo.udf_MinDate( @Date1 DATETIME, @Date2 DATETIME)
RETURNS DATETIME
AS
BEGIN
RETURN CASE WHEN @Date1 < @Date2 THEN @Date1 ELSE @Date2 END
END
GO
CREATE FUNCTION dbo.udf_MaxDate( @Date1 DATETIME, @Date2 DATETIME)
RETURNS DATETIME
AS
BEGIN
RETURN CASE WHEN @Date1 > @Date2 THEN @Date1 ELSE @Date2 END
END
GO
IF OBJECT_ID('tempdb..#TempResult') IS NOT NULL
DROP TABLE #TempResult
CREATE TABLE #TempResult ( Job NVARCHAR(256), ClockInDateTime DATETIME, ClockOutDateTime DATETIME, [Date] DATE, [Hours] DECIMAL(18,6))
DECLARE @MaxDaysDifferent INT, @CurDayOffset INT
SELECT @MaxDaysDifferent = MAX(DATEDIFF(dd, ClockInDateTime, ClockOutDateTime))
FROM #Table
SET @CurDayoffset = 0
WHILE (@CurDayOffset <= @MaxDaysDifferent)
BEGIN
INSERT INTO #TempResult (Job, ClockInDateTime, ClockOutDateTime, [Date], Hours)
SELECT T.Job, T.ClockInDateTime, T.ClockOutDateTime,
DATEADD(dd, @CurDayOffset, CONVERT(DATE,T.ClockInDateTime)),
DATEDIFF(mi, dbo.udf_MaxDate(T.ClockInDateTime, DATEADD(dd, @CurDayOffset, CONVERT(DATE,T.ClockInDateTime))),
dbo.udf_MinDate(T.ClockOutDateTime, DATEADD(dd, @CurDayOffset + 1, CONVERT(DATE,T.ClockInDateTime)))) / 60.0 as [Hours]
FROM #Table T
WHERE DATEADD(dd, @CurDayOffset, CONVERT(DATE,T.ClockInDateTime)) <= T.ClockOutDateTime
SET @CurDayOffset = @CurDayOffset + 1
END
SELECT * FROM #TempResult
ORDER BY Job, [Date]
此查询的完整结果是:
Method 1: Calculate as a subset of all possible days
Job ClockInDateTime ClockOutDateTime Date Hours
------------------------------ ----------------------- ----------------------- ---------- ------------------------------
MM00151509 2013-11-19 07:01:00.000 2013-11-19 09:20:00.000 2013-11-19 2.316666
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-09 14.866666
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-10 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-11 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-12 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-13 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-14 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-15 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-16 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-17 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-18 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-19 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-20 11.600000
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-01 15.666666
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-02 24.000000
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-03 8.250000
MM00154121 2013-12-05 08:19:00.000 2013-12-05 10:32:00.000 2013-12-05 2.216666
Method 2: Loop 1 day at a time
Job ClockInDateTime ClockOutDateTime Date Hours
------------------------------ ----------------------- ----------------------- ---------- ------------------------------
MM00151509 2013-11-19 07:01:00.000 2013-11-19 09:20:00.000 2013-11-19 2.316666
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-09 14.866666
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-10 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-11 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-12 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-13 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-14 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-15 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-16 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-17 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-18 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-19 24.000000
MM00151800 2013-11-09 09:08:00.000 2013-11-20 11:36:00.000 2013-11-20 11.600000
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-01 15.666666
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-02 24.000000
MM00153591 2013-12-01 08:20:00.000 2013-12-03 08:15:00.000 2013-12-03 8.250000
MM00154121 2013-12-05 08:19:00.000 2013-12-05 10:32:00.000 2013-12-05 2.216666
请注意,在第二种方法中,我介绍了一些udfs,只是为了让小时计算显得更清晰。
另请注意,这两种方法都会产生相同的结果(这次我在Sql Server 2012 Express上运行了示例,但我没有使用任何2012特定功能)。
最后,关于原始预期结果与小时数结果之间的差异。我相信您的预期结果不正确。 08:20到午夜之间的时间是15小时40分钟,即15.6667小时,而不是15.333小时。