如何在两个日期时间内获取2个日期时间并获得每个日期的小时数?

时间:2014-12-04 21:51:07

标签: sql-server sql-server-2008-r2

我在数据库中有以下数据:

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

1 个答案:

答案 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小时。