【问题标题】:DAY shift calculations白班计算
【发布时间】:2014-09-08 05:17:03
【问题描述】:

输入表

Start time     End Time
8/12/14 17:00  8/14/14 12:00

我需要显示如下输出

Date     11:00 to 23:00    23:00 to 11:00
8/12/14        6                1
8/13/14        12               12
8/14/14        1                11

我知道你们都会说“你们尝试了什么?”

但答案“我想不出从哪里开始”

【问题讨论】:

  • 如何将一天分为三个部分,0:00 到 11:00、11:00 到 23:00 和 23:00 到 24:00。计算每个部分的小时数后,将 0:00 到 11:00 的值和 23:00 到 24:00 的值相加即可得到 23:00 到 11:00 的值。
  • 请您澄清一下您的结果。你是怎么得到这个的?
  • @AlexanderFedorenko 这是不言自明的。我需要每天换班
  • 也许有一种方法可以在 1 个 SQL 语句中完成。我会像使用任何其他开发语言一样使用游标并在 T-SQL 中开发它:循环数据集,并在 T-SQL 中对其进行编码。
  • 在您的示例中,行 8/12/1423:00 to 11:00 是否应该是 12 而不是 1

标签: sql-server datetime sql-server-2012


【解决方案1】:

使用递归 CTE:

SQL Fiddle

MS SQL Server 2012 架构设置

查询 1

DECLARE @StartDate DateTime = '2014-08-12 17:00'
DECLARE @EndDate DateTime = '2014-08-14 12:00'


;WITH CTE
AS
(
    SELECT @StartDate AS [Date], 
           CAST(@StartDate As time) AS [Time]

    UNION ALL

    SELECT DATEADD(HH, 1, [Date]) As [Date], 
           CAST(DATEADD(HH, 1, [Date]) AS Time) AS [Time]
    FROM CTE
    WHERE DATEADD(HH, 1, [Date]) < @EndDate
)
SELECT CAST([Date] AS Date) [Date], 
        SUM(CASE WHEN [Time] BETWEEN '11:00' AND '22:59' 
                THEN 1 ELSE 0 END) as [11:00 to 23:00],
        SUM(CASE WHEN [Time] BETWEEN '00:00' AND '10:59' 
              THEN 1
                WHEN  [Time] Between '23:00' and '23:59' THEN 1
       ELSE 0 END) as [23:00 to 11:00]
FROM CTE
GROUP BY CAST([Date] AS Date) 
ORDER BY [Date]

Results

|       DATE | 11:00 TO 23:00 | 23:00 TO 11:00 |
|------------|----------------|----------------|
| 2014-08-12 |              6 |              1 |
| 2014-08-13 |             12 |             12 |
| 2014-08-14 |              1 |             11 |

【讨论】:

    【解决方案2】:

    以下显示了使用 Tally 表(又名“数字表”表)的一种方法。

    我做出的主要假设:

    • 开始时间和结束时间都是已知的
    • 开始和结束时间是精确的时间(例如 1:00、2:00,而不是 13:23 或 00:00.00.007)。 (这就是我使用 smalldatetime 数据类型的原因)
    • 结束时间总是大于开始时间

    我进一步假设数据存储在多个实体的表中 - 也就是说,您最终希望在一个查询中处理多个项目。如果您一次只想对一个项目执行此操作,(a) 下面的查询可以很容易地被砍掉,并且 (b) 在 C# 或任何调用语言中可能会更容易。

    设置测试数据:

    --  DROP TABLE Testing
    CREATE TABLE Testing
     (
       EntryId    int  not null
      ,StartTime  smalldatetime  not null
      ,EndTime    smalldatetime  not null
     )
    
    INSERT Testing values
      (1, 'Aug 12, 2014 17:00', 'Aug 14, 2014  12:00')  --  Original problem
     ,(2, 'Aug 11, 2014 00:00', 'Aug 11, 2014  23:00')  --  23 hours
     ,(3, 'Aug 11, 2014 00:00', 'Aug 12, 2014  00:00')  --  24 hour shift
     ,(4, 'Aug 11, 2014 12:00', 'Aug 12, 2014  12:00')  --  Noon to Noon
    
     ,(11, 'Aug 22, 2014 4:00',  'Aug 22, 2014  5:00')  --  One-hour problem cases
     ,(12, 'Aug 22, 2014 10:00', 'Aug 22, 2014  11:00') --  One-hour problem cases
     ,(13, 'Aug 22, 2014 11:00', 'Aug 22, 2014  12:00') --  One-hour problem cases
     ,(14, 'Aug 22, 2014 12:00', 'Aug 22, 2014  13:00') --  One-hour problem cases
    
     ,(21, 'Aug 23, 2014 18:00', 'Aug 23, 2014  19:00') --  One-hour problem cases
     ,(22, 'Aug 23, 2014 22:00', 'Aug 23, 2014  23:00') --  One-hour problem cases
     ,(23, 'Aug 23, 2014 23:00', 'Aug 24, 2014  00:00') --  One-hour problem cases
     ,(24, 'Aug 24, 2014 00:00', 'Aug 24, 2014   1:00') --  One-hour problem cases
    

    我的日常:

    DECLARE
      @Earliest smalldatetime
     ,@Latest   smalldatetime
    
    
    --  This could be thrown in as a first CTE, but doing so would make the overall query that much less comprehensible.
    SELECT
       @Earliest = min(StartTime)
      ,@Latest   = max(EndTime)
     from Testing
     --where <filtering criteria, if you're not parsing the whole table)
    
    ;WITH
      Pass0 as (select 1 as C union all select 1), --2 rows
      Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
      Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
      Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
      Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
      Tally as (select row_number() over(order by C) as Number from Pass4),
      DateRange as (select
                       dateadd(hh, ta.Number, @Earliest) ShiftHour
                     from Tally ta
                     where dateadd(hh, ta.Number, @Earliest) <= @Latest)
    SELECT
       te.EntryId
      ,cast(dateadd(hh, -1, dr.Shifthour) as date)  [Date]
      ,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 1 else 0 end)  [11:00 to 23:00]
      ,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 0 else 1 end)  [23:00 to 11:00]
     from Testing te
      inner join DateRange dr
       on dr.ShiftHour > te.StartTime
        and dr.ShiftHour <= te.Endtime
     group by
       te.EntryId
      ,cast(dateadd(hh, -1, dr.Shifthour) as date)
     order by 
       te.EntryId
      ,cast(dateadd(hh, -1, dr.Shifthour) as date)
    

    将其插入以显示没有分组的结果,这对于弄清楚其中发生了什么非常宝贵:

    ;WITH
      Pass0 as (select 1 as C union all select 1), --2 rows
      Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
      Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
      Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
      Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
      Tally as (select row_number() over(order by C) as Number from Pass4),
      DateRange as (select
                       dateadd(hh, ta.Number, @Earliest) ShiftHour
                     from Tally ta
                     where dateadd(hh, ta.Number, @Earliest) <= @Latest)
    SELECT
        te.EntryId
       ,dateadd(hh, -1, dr.Shifthour)
       ,cast(dateadd(hh, -1, dr.Shifthour) as date)  [Date]
       ,datepart(hh, dateadd(hh, -1, dr.Shifthour))
      from Testing te
       inner join DateRange dr
        on dr.ShiftHour > te.StartTime
         and dr.ShiftHour <= te.Endtime
      order by 
        te.EntryId
       ,cast(dateadd(hh, -1, dr.Shifthour) as date)
       ,dateadd(hh, -1, dr.Shifthour)
    

    困难的部分是:

    • 将第 24 小时(8 月 23 日,00:00)获取到前一天(8 月 24 日)。这就是为什么我把所有的东西都往后移一天 [编辑] 一小时
    • &lt;&gt; 处理多个条目时的逻辑
    • 接受“一天中午到中午”样本的结果看起来很傻的事实。

    是的,这可能是过度设计,但有时您只需要尝试一下即可。我不能说它在大型数据集上的表现如何,如果您的第一次和最后一次日期相隔超过四年,您需要在构建计数表时添加“Pass5”。

    【讨论】:

      【解决方案3】:
      DECLARE @StartTime DATETIME='08/01/2013 00:00:00'
          ,@EndTime DATETIME='08/03/2013 23:00:00'
      DECLARE @StartDate DATE,
          @Starthour INT,
          @Startmin INT,
          @EndDate DATE,
          @Endhour INT,
          @Endmin INT
      
      SELECT @StartDate=CONVERT(DATE,@StartTime,101),@Starthour=DATEPART(HOUR,@StartTime),@Startmin=DATEPART(MINUTE,@StartTime)
      SELECT @EndDate=CONVERT(DATE,@EndTime,101),@Endhour=DATEPART(HOUR,@EndTime),@Endmin=DATEPART(MINUTE,@EndTime)
      
      DECLARE @T TABLE (StartTime DATETIME, EndTime DATETIME)
      INSERT @T VALUES (DATEADD(MINUTE,-@Startmin,@StartTime), DATEADD(MINUTE,-@Endmin,@EndTime))
      
      DECLARE @Final1 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
      DECLARE @Final2 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
      DECLARE @Final3 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
      
      ;WITH CTE AS
      (
          SELECT StartTime AS dt, EndTime,StartTime
          FROM @T
          UNION  ALL
          SELECT DATEADD(MINUTE, 60, dt) AS dt, EndTime,DATEADD(MINUTE, 60,StartTime) AS StartTime
          FROM CTE
          WHERE dt < DATEADD(HOUR, -1, EndTime)
      )
      INSERT INTO @Final1([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
      SELECT 
          CASE 
            WHEN CAST(dt AS TIME) >= '11:00' AND CAST(dt AS TIME) < '23:00' THEN 1 
            ELSE 0 
          END AS '11:00 to 23:00',
          CASE 
            WHEN CAST(dt AS TIME) >= '11:00' AND CAST(dt AS TIME) < '23:00' THEN 0 
            ELSE 1 
          END AS '23:00 to 11:00'
          ,StartTime
      FROM CTE
      
      
      INSERT INTO @Final2([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
      SELECT SUM([11:00 to 23:00])*60 AS [11:00 to 23:00]
          ,SUM([23:00 to 11:00])*60 AS [23:00 to 11:00]
          ,CONVERT(DATE,DATEADD(MINUTE,-660,ShiftDate)) AS ShiftDate
      FROM @Final1
      GROUP BY CONVERT(DATE,DATEADD(MINUTE,-660,ShiftDate))
      
      
      INSERT INTO @Final3([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
      SELECT CASE WHEN ShiftDate=@StartDate AND @Starthour BETWEEN 11 AND 22 THEN [11:00 to 23:00]-@Startmin ELSE [11:00 to 23:00] END AS [11:00 to 23:00],
              CASE WHEN ShiftDate=@StartDate AND @Starthour BETWEEN 23 AND 10 THEN [23:00 to 11:00]-@Startmin ELSE [23:00 to 11:00] END AS [23:00 to 11:00],
              ShiftDate
      FROM @Final2
      
      
      SELECT ShiftDate,
          CONVERT(VARCHAR(10),
                      (CASE WHEN ShiftDate=@EndDate AND @Endhour BETWEEN 11 AND 22 
                          THEN [11:00 to 23:00]+@Endmin 
                      ELSE [11:00 to 23:00] END)) AS [11:00 to 23:00],
          CONVERT(VARCHAR(10),
                  (CASE WHEN ShiftDate=@EndDate AND @Endhour BETWEEN 23 AND 10 
                      THEN [23:00 to 11:00]+@Endmin 
                  ELSE [23:00 to 11:00] END)) AS [23:00 to 11:00]
      FROM @Final3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-24
        • 2018-11-29
        • 2016-10-16
        相关资源
        最近更新 更多