查找一天中花费的时间以及休息时间

Posted

技术标签:

【中文标题】查找一天中花费的时间以及休息时间【英文标题】:Find time spent in a day along with work break taken 【发布时间】:2018-08-09 07:05:12 【问题描述】:

我需要了解一些内部应用程序在办公室花费的总时间。

我有这样的示例数据:

Id  EmployeeId  ScanDateTime    Status
 7  87008   2018-08-02 16:03:00.227 1
 8  87008   2018-08-02 16:06:17.277 2
 9  87008   2018-08-02 16:10:37.107 3
 10 87008   2018-08-02 16:20:17.277 2
 11 87008   2018-08-02 16:30:37.107 3
12  87008   2018-08-02 20:06:00.000 4

这里的Status有不同的含义:

1- 开始 2-暂停 3- 简历 4-结束

表示员工在状态为 1 时在 ScanDateTime 开始工作。他们可以休息(状态 2)然后回来继续工作(状态 3),状态 4 表示他们正在结束工作。 注意:工作时间可能会有多次休息。

预期输出:

EmployeeId  StartTime                 EndTime                  BreakInMins 
87008       2018-08-02 16:03:00.227   2018-08-02 20:06:00.000   14

我尝试按照一些示例来计算预期结果集,但没有帮助。

我找不到任何类似示例可用的示例。

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

请试试这个。处理多个休息/员工和案例,当休息仍在进行中或会话未完成时

select
     [EmployeeId]   =   [s].[EmployeeId]
    ,[StartTime]    =   [s].[ScanDateTime]
    ,[EndTime]      =   [et].[ScanDateTime]
    ,[BreakInMins]  =   [b].[BreakInMins]
from
    [Scans] as  [s] --  here is your table
outer apply
    (      
        select top 1 [ScanDateTime], [Id] from [Scans] where [Id] > [s].[Id] and [EmployeeId] = [s].[EmployeeId] and [Status] = 4 order by [ScanDateTime] asc
    )       as  [et]
outer apply
    (
        select
              [BreakInMins] = sum(isnull([r].[mins], datediff(mi, [sp].[ScanDateTime], getdate())))
        from
            [Scans] as [sp]
        outer apply
            (
                select top 1 [mins] = datediff(mi, [sp].[ScanDateTime], [ScanDateTime]) from [Scans] where [Id] > [sp].[Id] and [EmployeeId] = [sp].[EmployeeId] and [Status] IN (3, 4) order by [ScanDateTime] asc
            ) as [r]
        where
                [sp].[id] > [s].[id] and [sp].[id] < isnull([et].[id], [id] + 1)
            and [sp].[EmployeeId] = [s].[EmployeeId]
            and [sp].[Status] = 2

    )       as  [b]    
where
        [Status] = 1;

这是测试友好的脚本:script

【讨论】:

我也看到了你的查询,它计算的休息时间不正确。 我的查询返回 14 分钟,正如您的问题所预期的那样。如果你给我一些关于潜在错误的提示,我会修复它;) 呃,我修复了帖子中的错误,但pastebin内容仍然存在错误...这是新的测试代码,请尝试:pastebin.com/g58zAqTt 修复了另一个错误:当会话结束时没有先前的状态为“恢复” 让我们continue this discussion in chat.【参考方案2】:

我考虑员工每天有多次休息,您可以在下面查看我还提供了小提琴链接

select t1.*,t5.breakmins from
(
  select EmployeeId,min(StartTime) as StartTime,max(EndTime) as EndTime from 
(
  select EmployeeId,(case when status=1 then ScanDateTime end) as StartTime,
(case when status=4 then ScanDateTime end) as EndTime,
case when status=3 then ScanDateTime end as ResumeWork,
             case when status=2 then ScanDateTime end as pauseTime
    from emp

    ) as t group by EmployeeId
  )  t1
inner join
(
select EmployeeId, convert(date,ResumeWork) as day ,
 sum(case when status=2 then datediff(minute,ResumeWork,res)  end ) as breakmins from
(
select EmployeeId,ResumeWork,status ,
lag(ResumeWork) over(PARTITION BY EmployeeId order by ResumeWork desc) as res from 
(
 select * from 
(
select EmployeeId, case when status=3 then ScanDateTime end as ResumeWork,status from emp
) as t1 where ResumeWork is not null


    union all 
    select * from 
(
    select EmployeeId,case when status=2 then ScanDateTime end as pauseTime,status from emp
  ) as t2 where pauseTime is not null
 )  as t3 group by EmployeeId,ResumeWork,status
  ) t4 group by EmployeeId, convert(date,ResumeWork)
  )t5 on t1.EmployeeId=t5.EmployeeId
  and  convert(date,t1.StartTime)=t5.day


EmployeeId     StartTime                  EndTime          breakmins
87008   2018-08-02T16:03:00.227Z    2018-08-02T20:06:00Z    12

http://sqlfiddle.com/#!18/ae60f/6

【讨论】:

感谢@Zaynul 的回答。你的回答没问题,但我不是很多子查询和连接的忠实粉丝。我仍然喜欢你准确理解问题的方式。【参考方案3】:

你可以试试这个。 通过StatusCTE 中创建一个row_number,因为我们需要知道哪个暂停时间对应哪个恢复时间。然后self joinCTE by EmployeeId

CREATE TABLE T(
   Id  INT,
   EmployeeId INT,
   ScanDateTime DATETIME,
  Status INT
);


INSERT INTO T VALUES (7 ,87008 ,'2018-08-02 16:03:00.227',1);
INSERT INTO T VALUES (8 ,87008 ,'2018-08-02 16:06:17.277',2);
INSERT INTO T VALUES (9 ,87008 ,'2018-08-02 16:10:37.107',3);
INSERT INTO T VALUES (10,87008 ,'2018-08-02 16:20:17.277',2);
INSERT INTO T VALUES (11,87008 ,'2018-08-02 16:30:37.107',3);
INSERT INTO T VALUES (12,87008 ,'2018-08-02 20:06:00.000',4);

查询 1

;with cte as(
  SELECT *,
         MIN(ScanDateTime) over(partition by EmployeeId order by EmployeeId) StartTime,
         MAX(ScanDateTime) over(partition by EmployeeId order by EmployeeId) EndTime,
         ROW_NUMBER() OVER(PARTITION BY Status order by id) rn
  FROM t
)
select t1.EmployeeId,
       t1.StartTime,
       t1.EndTime,
       SUM(datediff(minute,t1.ScanDateTime,t2.ScanDateTime)) BreakInMins 
from 
cte t1 
inner join cte t2
on t1.rn =t2.rn and t1.Status = 2 and t2.Status = 3 and t1.EmployeeId = t2.EmployeeId
group by t1.EmployeeId,
       t1.StartTime,
       t1.EndTime

Results

| EmployeeId |            StartTime |                  EndTime | BreakInMins |
|------------|----------------------|--------------------------|-------------|
|      87008 | 2018-08-02T20:06:00Z | 2018-08-02T16:03:00.227Z |          14 |

编辑

如果您的数据中有不同的日期,您可以尝试此查询。只需按date 分组即可。

CREATE TABLE T(
   Id  INT,
   EmployeeId INT,
   ScanDateTime DATETIME,
  Status INT
);


INSERT INTO T VALUES (7 ,87008 ,'2018-08-02 16:03:00.227',1);
INSERT INTO T VALUES (8 ,87008 ,'2018-08-02 16:06:17.277',2);
INSERT INTO T VALUES (9 ,87008 ,'2018-08-02 16:10:37.107',3);
INSERT INTO T VALUES (10,87008 ,'2018-08-02 16:20:17.277',2);
INSERT INTO T VALUES (11,87008 ,'2018-08-02 16:30:37.107',3);
INSERT INTO T VALUES (12,87008 ,'2018-08-02 20:06:00.000',4);
INSERT INTO T VALUES (27 ,87008 ,'2018-08-03 16:03:00.227',1);
INSERT INTO T VALUES (28 ,87008 ,'2018-08-03 16:06:17.277',2);
INSERT INTO T VALUES (29 ,87008 ,'2018-08-03 16:11:37.107',3);
INSERT INTO T VALUES (210,87008 ,'2018-08-03 16:20:17.277',2);
INSERT INTO T VALUES (211,87008 ,'2018-08-03 16:30:37.107',3);
INSERT INTO T VALUES (212,87008 ,'2018-08-03 20:06:00.000',4);

查询 1

;with cte as(
  SELECT EmployeeId,
         MAX(CASE WHEN Status = 1 then ScanDateTime end) StartTime,
         MIN(CASE WHEN Status = 4 then ScanDateTime end) EndTime,
         CAST(ScanDateTime as date) dt
  FROM t
  GROUP BY EmployeeId,CAST(ScanDateTime as date)
)
,cte2 as(
  SELECT t2.*,
         Row_number() over(partition by t2.EmployeeId,t2.Status order by Id) rn,
         t1.StartTime,
         t1.EndTime,
         t1.dt
  FROM cte t1 
  INNER JOIN T t2 ON t1.EmployeeId = t2.EmployeeId and Status in (2,3) and t1.dt =  CAST(t2.ScanDateTime as date)
)
select t1.EmployeeId,
       t1.StartTime,
       t1.EndTime,
       SUM(datediff(minute,t1.ScanDateTime,t2.ScanDateTime)) BreakInMins 
from cte2 t1 
inner join cte2 t2 on 
  t1.rn = t2.rn 
and 
  t1.EmployeeId = t2.EmployeeId
and t1.Status = 2 and t2.Status =3
group by t1.EmployeeId,
       t1.StartTime,
       t1.EndTime

Results

| EmployeeId |                StartTime |              EndTime | BreakInMins |
|------------|--------------------------|----------------------|-------------|
|      87008 | 2018-08-02T16:03:00.227Z | 2018-08-02T20:06:00Z |          14 |
|      87008 | 2018-08-03T16:03:00.227Z | 2018-08-03T20:06:00Z |          15 |

【讨论】:

您查询中的一个小问题,但几乎解决了我的问题。我喜欢查询的编写方式。小问题是 startTime 和 EndTime 在您的查询中被交换了。 @error_handler:对您接受正确答案的方法感到沮丧。当前解决方案存在多个问题: 1. 会话未结束时,恢复日期错误地显示为结束日期。 2 当会话暂停时,我们仍然处于休息状态,休息时间计算错误。 3 个严重错误:当您完成同一员工的两个会话时,一个会话显示分钟总和而不是两个。还有其他一些问题。 嘿@Juozas,不要沮丧。但目前选择的答案解决了我的问题,这就是我接受它作为正确答案的原因。对不起伙计。但我喜欢你指出计算流程的方式。该流程正在处理中,而不是在查询中。感谢您的努力。 @error_handler 请记住:通过使用“正确”答案,您始终拥有一个员工会话作为摘要记录。例如,员工整月每天都在工作。每天 8:00 至 17:00。通过使用它,您将拥有一个整月的会话记录。但是你每个月有大约 30 次会议,对吧?所以,要小心。以及我之前提到的其他错误。 嘿@Juozas,你是对的,让我用更多数据测试这个查询。如果我发现其他问题,我会更新你。【参考方案4】:

试试下面的查询:http://sqlfiddle.com/#!18/6fe11/3

   select id,min(case when status=1 then stattime end) as starttime,
min(case when status=4 then stattime end) as endtime,
sum(case when status=2 then minute end) as breakinmin
from
(
select id,stattime,status,
DATEdiff(minute,stattime,lead(stattime,1,NULL) 
         over (partition by id ORDER BY stattime)) as minute
    from ForgeRock)a
    group by id

id      starttime                   endtime                 breakinmin
87008   2018-08-02T16:03:00.227Z    2018-08-02T20:06:00Z    14

【讨论】:

嘿,这行不通。它不会计算 WorkBreakInMins。它总是生成 null。 现在检查,修改查询并给出 sqlfiddle 链接 当您将 min() 从 Pause 或 Resume 类型中取出时,即使多次中断也会失败。 你那里有breakid吗 不完全是,我在我的问题样本输入和预期输出中添加了更多值。

以上是关于查找一天中花费的时间以及休息时间的主要内容,如果未能解决你的问题,请参考以下文章

javascript BlueKai CoreTag - 每次访问花费的时间,每个会话的页数,一天中的时间

如何使数字显示为 hh:mm(持续时间,而不是一天中的时间)

是否可以显示线性搜索以查找您输入的密钥以在程序中查找所花费的时间? [关闭]

在 SQL 中查找按日期分组的集合中的平均最低项

查找每行的最小值和最大值,不包括 NaN 值

查找id在每个位置花费的时间