SQL TSQL 工人每小时

SQL TSQL for Workers per Hour

我有一个带有指纹时间戳的日志如下:

Usr   TimeStamp
-------------------------
 1    2015-07-01 08:01:00
 2    2015-07-01 08:05:00
 3    2015-07-01 08:07:00
 1    2015-07-01 10:05:00
 3    2015-07-01 11:00:00
 1    2015-07-01 12:01:00
 2    2015-07-01 13:03:00
 2    2015-07-01 14:02:00
 1    2015-07-01 16:03:00
 2    2015-07-01 18:04:00

我希望每小时产出工人数(四舍五入到最接近的小时数) 理论输出应该是:

 7:00  0
 8:00  3
 9:00  3
 10:00 2
 11:00 1
 12:00 2
 13:00 1
 14:00 2
 15:00 2
 16:00 1
 17:00 1
 18:00 0
 19:00 0

任何人都可以考虑如何通过 SQL 或如果没有其他方法,通过 TSQL 来解决这个问题?

编辑:时间戳是不同用户的登录和注销。因此,在早上 8 点,有 3 个用户登录,而同样的 3 个用户在上午 9 点仍在工作。其中一个在上午 10 点离开。等等

首先,您可以使用 datepart 获取以下日期的小时数,然后按用户分组

SELECT DATEPART(HOUR, GETDATE());

SQL Fiddle

SELECT Convert(varchar(5),DATEPART(HOUR, timestamp)) + ':00' as time,
count(usr) as users
from tbl
group by DATEPART(HOUR, timestamp)

您需要 datetime hour table 才能执行此操作。

注意:这只是一个示例,展示了查询在一天内应该如何工作。将 CTE 替换为 datetime hour table。在 datetime hour table 中,每个日期都应以 07:00:00 小时开始并以 19:00:00 小时结束

如果您想在一天以上执行此操作,则可能需要在 selectgroup by 中包含 Cast(dt.date_time AS DATE) 以区分该小时属于哪一天

WITH datetime_table 
     AS (SELECT '2015-07-01 07:00:00' AS date_time 
         UNION ALL 
         SELECT '2015-07-01 08:00:00' 
         UNION ALL 
         SELECT '2015-07-01 09:00:00' 
         UNION ALL 
         SELECT '2015-07-01 10:00:00' 
         UNION ALL 
         SELECT '2015-07-01 11:00:00' 
         UNION ALL 
         SELECT '2015-07-01 12:00:00' 
         UNION ALL 
         SELECT '2015-07-01 13:00:00' 
         UNION ALL 
         SELECT '2015-07-01 14:00:00' 
         UNION ALL 
         SELECT '2015-07-01 15:00:00' 
         UNION ALL 
         SELECT '2015-07-01 16:00:00' 
         UNION ALL 
         SELECT '2015-07-01 17:00:00' 
         UNION ALL 
         SELECT '2015-07-01 18:00:00' 
         UNION ALL 
         SELECT '2015-07-01 19:00:00') 
SELECT Datepart(hour, dt.date_time), 
       Hour_count=Count(t.id) 
FROM   datetime_table dt 
       LEFT OUTER JOIN Yourtable t 
                    ON Cast(t.dates AS DATE) = Cast(dt.date_time AS DATE) 
                       AND Datepart(hour, t.dates) = 
                           Datepart(hour, dt.date_time) 
GROUP  BY Datepart(hour, dt.date_time) 

您只需按时间和日期分组。检查以下查询,希望对您有所帮助:

Create table #t1
(
usr int,
timelog datetime
)

 Insert into #t1 values(1, '2015-07-01 08:01:00')
 Insert into #t1 values(2,    '2015-07-01 08:05:00')
 Insert into #t1 values(3,    '2015-07-01 08:07:00')
 Insert into #t1 values(1,    '2015-07-01 10:05:00')
 Insert into #t1 values(3,    '2015-07-01 11:00:00')
 Insert into #t1 values(1,    '2015-07-01 12:01:00')
 Insert into #t1 values(2,    '2015-07-01 13:03:00')
 Insert into #t1 values(2,    '2015-07-01 14:02:00')
 Insert into #t1 values(1,    '2015-07-01 16:03:00')
 Insert into #t1 values(2,    '2015-07-01 18:04:00')

Select cast(timelog as varchar(11)) as LogDate, Datepart(hour, timelog) as LogTime, count(usr) as UserCount from #t1
Group by Datepart(hour, timelog), cast(timelog as varchar(11))

更难的部分是在数据缺失的地方创建零。通常的方法是生成所有可能 "slots" 的列表,然后对实际数据进行外部连接。我假设您一次只想 运行 这一天。

我的方法(这只是一个示例)之所以有效,是因为它交叉连接了分别具有 6 行和 4 行的两个表,并且 6 乘以 4 等于 24。

select f1.d * 6 + f0.d, coalesce(data.cnt, 0)
from
    (
        select 0 as d union all select 1 union all select 2 union all 
        select 3 union all select 4 union all select 5
    ) as f0,

    (
        select 0 as d union all select 1 union all
        select 2 union all select 3
    ) as f1
    left outer join
    (
        select
            cast(datepart(hh, TimeStamp) as varchar(2)) + ':00' as hr,
            count(*) as cnt
        from LOG
        group by datepart(hh, TimeStamp)
    ) as data
        on data.hr = f1.d * 6 + f0.d

首先你需要将时间四舍五入到最接近的小时

DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0)

正如你首先看到的,我们在原来的时间基础上增加了 30 分钟(DATEADD(MI, 30, TimeStamp))
这种方法也会将 08:04 舍入到 08:00 或将 07:58 舍入到 8:00。 正如我假设一些工人可以早点开始工作

SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0) As FingertipTime
FROM Fingertips

如果你经常使用舍入时间戳,你可以创建一个Computed column

ALTER TABLE Fingertips ADD RoundedTimeStamp AS (DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, TimeStamp)), 0));

要将时间戳与工作时间常数进行比较,您可以找到不同的方法。我将使用 TABLE 类型的变量,在其中生成当天的工作时间
然后使用 LEFT JOINGROUP BY 我们得到时间戳的数量

DECLARE @WorkHours TABLE(WorkHour DATETIME)
INSERT INTO @WorkHours (WorkHour) VALUES
('2015-07-01 07:00'),
('2015-07-01 08:00'),
('2015-07-01 09:00'),
('2015-07-01 10:00'),
('2015-07-01 11:00'),
('2015-07-01 12:00'),
('2015-07-01 13:00'),
('2015-07-01 14:00'),
('2015-07-01 15:00'),
('2015-07-01 16:00'),
('2015-07-01 17:00'),
('2015-07-01 18:00'),
('2015-07-01 19:00')

SELECT wh.Workhour
, COUNT(ft.TimeStamp) As Quantity
FROM @WorkHours wh
LEFT JOIN Fingertips ft ON ft.RoundedTimeStamp = wh.WorkHour
GROUP BY wh.WorkHour

勾选这个SQL Fiddle

这是我的最终工作代码:

create table tsts(id int, dates datetime)
insert tsts values
(1 ,   '2015-07-01 08:01:00'),
(2 ,   '2015-07-01 08:05:00'),
(3 ,   '2015-07-01 08:07:00'),
(1 ,   '2015-07-01 10:05:00'),
(3 ,   '2015-07-01 11:00:00'),
(1 ,   '2015-07-01 12:01:00'),
(2 ,   '2015-07-01 13:03:00'),
(2 ,   '2015-07-01 14:02:00'),
(1 ,   '2015-07-01 16:03:00'),
(2 ,   '2015-07-01 18:04:00')

select horas.hora, isnull(sum(math) over(order by horas.hora rows unbounded preceding),0) as Employees from

(
select 0 as hora union all
select 1 as hora union all
select 2 as hora union all
select 3 as hora union all
select 4 as hora union all
select 5 as hora union all
select 6 as hora union all
select 7 as hora union all
select 8 as hora union all
select 9 as hora union all
select 10 as hora union all
select 11 as hora union all
select 12 as hora union all
select 13 as hora union all
select 14 as hora union all
select 15 as hora union all
select 16 as hora union all
select 17 as hora union all
select 18 as hora union all
select 19 as hora union all
select 20 as hora union all
select 21 as hora union all
select 22 as hora union all
select 23
) as horas

left outer join 
(
select hora, sum(math) as math from 
    (
    select id, hora, iif(rowid%2 = 1,1,-1) math from 
        (
        select row_number() over (partition by id order by id, dates) as rowid, id, datepart(hh,dateadd(mi, 30, dates)) as hora from tsts 
        ) as Q1
    ) as Q2
group by hora
) as Q3 

on horas.hora = Q3.hora

SQL Fiddle

许多独立的部分必须粘合在一起才能完成。 第一舍入,这很容易通过获取日期的小时部分 + 30 分钟来完成。然后确定开始和结束记录。如果没有字段表明这一点并且假设一天的第一次出现是登录或开始,您可以使用 row_number 并使用奇数作为开始记录。

然后必须耦合开始和结束,在 sql 服务器 2012 和更高版本中,这可以使用 lead 函数

轻松完成

要获取缺失的小时数,必须创建包含所有小时数的序列。有几个选项(好的 link here),但我喜欢在 table 上使用 row_number 的方法,它肯定包含足够的行(有一个适当的列用于order by),如sys.all_objects中使用的link。这样,7 到 19 小时可以创建为:select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects

如果只有一个日期要检查,查询可以在时间戳指纹的小时简单左连接。如果有更多日期,可以创建第二个序列交叉应用到时间以获得所有日期。假设只有一个日期,最终代码将是:

declare @t table(Usr int, [timestamp] datetime)

insert @t values
(1  ,   '2015-07-01 08:01:00'),
(2 ,   '2015-07-01 08:05:00'),
(3 ,   '2015-07-01 08:07:00'),
(1 ,   '2015-07-01 10:05:00'),
(3 ,   '2015-07-01 11:00:00'),
(1 ,   '2015-07-01 12:01:00'),
(2 ,   '2015-07-01 13:03:00'),
(2 ,   '2015-07-01 14:02:00'),
(1 ,  '2015-07-01 16:03:00'),
(2 ,   '2015-07-01 18:04:00'),
(2 ,   '2015-07-01 18:04:00')

;with usrHours as
(
    select Usr, datepart(hour, DATEADD(minute,30, times.timestamp)) [Hour] --convert all times to the rounded hour (rounding by adding 30 minutes)
     , ROW_NUMBER() over (partition by usr order by [timestamp] ) rnr
    from @t times --@t should be your logging table
), startend as --get next (end) hour by using lead
(
    select Usr, [hour] StartHour , LEAD([Hour]) over (partition by usr order by rnr) NextHour  ,rnr
    from usrHours
),hours as --sequence of hours 7 to 19
(
    select top 13 ROW_NUMBER() over (order by object_id) + 6 [Hour] from sys.all_objects
)

select cast([Hour] as varchar) + ':00' [Hour], COUNT(startend.usr) Users 
from hours --sequence is leading
left join startend on hours.Hour between startend.StartHour and startend.NextHour 
                                and rnr % 2  = 1 --every odd row number is a start time
group by Hours.hour