【问题标题】:Calculate attendance based on shift wise using sql使用sql根据班次计算出勤率
【发布时间】:2019-09-10 08:30:10
【问题描述】:

我有以下表格 trnevents 用于日志打孔:

emp_reader_id   DT
1066    2019-08-10 19:02:41.000
1066    2019-08-10 07:10:41.000
1066    2019-08-10 07:02:41.000
1066    2019-08-09 18:59:02.000
1066    2019-08-09 07:00:12.000
1066    2019-08-08 18:57:49.000
1066    2019-08-08 07:02:14.000
1066    2019-08-07 18:55:37.000
1066    2019-08-07 07:01:27.000
1066    2019-08-06 18:57:29.000
1066    2019-08-06 07:01:10.000

以下是按日期分配给员工的轮班时间表:

emp_reader_id   5   6   7   8   9   10  11  
   1066         14  14  14  14  14  13  13

Shift_days:

shift_id    day_start_time  night_shift day_end_time
      13       7.00            0           19.00
      14       19.00           1           7.00

我的查询:

update A set Belongs_to=CONVERT(VARCHAR(10),DATEADD(d, -1, A.DT) , 23),EventCatId=1
      from dbo.trnevents A    
      LEFT JOIN dbo.shift_schedule SS ON A.emp_reader_id = SS.emp_reader_id 
      AND YEAR(DATEADD(d, 0, A.DT))=SS.year_no AND MONTH(DATEADD(d, 0, A.DT))=SS.month_no
      LEFT JOIN dbo.shift  ON dbo.shift.shift_id = CASE      WHEN DAY(DATEADD(d, 0, A.DT))=1 THEN SS.[1]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=2 THEN SS.[2]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=3 THEN SS.[3]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=4 THEN SS.[4]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=5 THEN SS.[5]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=6 THEN SS.[6]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=7 THEN SS.[7]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=8 THEN SS.[8]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=9 THEN SS.[9]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=10 THEN SS.[10]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=11 THEN SS.[11]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=12 THEN SS.[12]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=13 THEN SS.[13]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=14 THEN SS.[14]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=15 THEN SS.[15]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=16 THEN SS.[16]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=17 THEN SS.[17]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=18 THEN SS.[18]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=19 THEN SS.[19]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=20 THEN SS.[20]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=21 THEN SS.[21]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=22 THEN SS.[22]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=23 THEN SS.[23]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=24 THEN SS.[24]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=25 THEN SS.[25]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=26 THEN SS.[26]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=27 THEN SS.[27]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=28 THEN SS.[28]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=29 THEN SS.[29]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=30 THEN SS.[30]
                                                                WHEN DAY(DATEADD(d, 0, A.DT))=31 THEN SS.[31] END
      LEFT JOIN dbo.shift_days ON dbo.shift.shift_id = dbo.shift_days.shift_id 
      and day_id = datepart(dw,CONVERT(VARCHAR(10),  DATEADD(d, 0, A.DT), 23))
     where CONVERT(VARCHAR(10), A.DT, 23) between  CONVERT(VARCHAR(10), DATEADD(d, 1, @start_date), 23) and CONVERT(VARCHAR(10), DATEADD(d, 1, @end_date), 23) 
          and A.DT < isnull(DATEADD(D, 0, DATEDIFF(D, 0, DATEADD(d, 0, CONVERT(VARCHAR(10), A.DT, 23))))+
                              CONVERT(CHAR(8), DATEADD(SECOND, 
                                 DATEDIFF(SECOND, '02:00:00.0000000',cast(replace(cast(dbo.shift_days.day_start_time as varchar(7)),'.',':') as time)), 
                                      '00:00:00'), 8),(DATEADD(d, 0, CONVERT(VARCHAR(10), A.DT, 23))  + '10:00:00')) 
  and  A.emp_reader_id in (select distinct emp_reader_id from daily_attendance_data
  where CONVERT(VARCHAR(10),att_date, 23) =  CONVERT(VARCHAR(10), DATEADD(d, -1, A.DT), 23) and night_shift=1)

我的输出:

emp_reader_id      DT                Belongs_to
    1066    2019-08-10 07:02:41     2019-08-10
    1066    2019-08-09 18:59:02     2019-08-09
    1066    2019-08-09 07:00:12     2019-08-08
    1066    2019-08-08 18:57:49     2019-08-08
    1066    2019-08-08 07:02:14     2019-08-07
    1066    2019-08-07 18:55:37     2019-08-07

第九天夜班-[18:59]-[7:02] 第10天轮班。 [7:00]-[19:00] 但属于日期未更新到 9th ,它分配到 10th。

2019-08-09 18:59:02.000 到 2019-08-10 07:02:41.000

我的预期输出:

 emp_reader_id      DT                Belongs_to
     1066    2019-08-10 19:02:41     2019-08-10
     1066    2019-08-10 07:05:41     2019-08-10
     1066    2019-08-10 07:02:41     2019-08-09
     1066    2019-08-09 18:59:02     2019-08-09
     1066    2019-08-09 07:00:12     2019-08-08
     1066    2019-08-08 18:57:49     2019-08-08
     1066    2019-08-08 07:02:14     2019-08-07
     1066    2019-08-07 18:55:37     2019-08-07

请帮我解决这个问题。

【问题讨论】:

  • 为什么Shift Schedule中的行是重复的?
  • 预期输出的第 2 行和第 3 行。两者都有相同的DT2019-08-10 07:xx 为什么一个属于2019-08-10 而另一个属于2019-08-09
  • @Squirrel 2019-08-09 18:59:02 夜班在 mrng 07:02:41 结束,他又开始了他的下一个职责 07:05:41 - 19:02:41 全天转变
  • 你怎么知道07:02:41 正在结束而不是开始?你的班次不是随着时间的推移是07:00 吗?那么它不应该是转变的开始吗?
  • 表的关系是什么,什么是班次表,与其他表有什么关系,目前还不是很清楚。您显示输出,但您的主要查询是更新,它应该是 SELECT 吗?请发布表结构,甚至可以创建一个 SQLFIDDLE 示例。

标签: sql sql-server


【解决方案1】:

案例1#:假设Emp Actual In time是19:00,Log in time是 2019-08-06 17:00:00,那你要判断这是否是早来的 2019-08-06 或随着时间的推移2019-08-05。这样的例子很多,

你必须对此制定一定的规则。

在我的脚本中,我使用了 MarginShiftStartMarginShiftEnd 列。 所以假设Actual In time 是19:00,Emp log In 时间是2019-08-06 16:00:00。 然后我的脚本会明白2019-08-06 上的时间是16:00:00,而且是早到(180 分钟或 3 小时)。

这清楚与否?

所以你必须为它确定一定的时间。

declare @From Datetime='2019-08-01'
declare @To Datetime='2019-08-31'

 IF OBJECT_ID('tempdb..#Calendar', 'U') IS NOT NULL
DROP TABLE #Calendar;
IF OBJECT_ID('tempdb..#temp', 'U') IS NOT NULL
DROP TABLE #temp;

IF OBJECT_ID('tempdb..#Shift', 'U') IS NOT NULL
DROP TABLE #Shift;

IF OBJECT_ID('tempdb..#Emp_Shift', 'U') IS NOT NULL
DROP TABLE #Emp_Shift;

declare @MarginStart int=4*60
declare @MarginEnd int=8*60

create table #Emp_Shift(emp_reader_id int,ShiftDays int,ShiftID int)
insert into #Emp_Shift values(1066,5,14),(1066,6,14),(1066,7,14)
,(1066,8,14),(1066,9,14),(1066,10,13),(1066,11,13)


create table #Shift(shift_id int,day_start_time time(0)
, night_shift bit,day_end_time time(0),DutyMinute int)
insert into #Shift(shift_id,day_start_time,night_shift,day_end_time,DutyMinute)
values
      (13,cast('07:00' as time(0)) ,0,cast('19:00'as time(0)),12*60)
     ,(14,cast('19:00' as time(0)),1,cast('07:00'as time(0)),12*60)


Create table #Calendar(AttnDt Datetime)
insert into #Calendar(AttnDt)
select Dates from CalendarDate where dates between @From and @To

create table #temp (emp_reader_id int, DT  datetime)
insert into #temp values
 (1066,'2019-08-10 19:02:41') 
,(1066,'2019-08-10 07:05:41') 
,(1066,'2019-08-10 07:02:41') 
,(1066,'2019-08-09 18:59:02') 
,(1066,'2019-08-09 07:00:12') 
,(1066,'2019-08-08 18:57:49') 
,(1066,'2019-08-08 07:02:14') 
,(1066,'2019-08-07 18:55:37') 


;
WITH CTE
     AS (SELECT AttnDt,
                emp_reader_id,
                ShiftID,
                AttnDt + CAST(day_start_time AS DATETIME) AS ActualShiftStart,
                DATEADD(MINUTE, DutyMinute, (AttnDt + CAST(day_start_time AS DATETIME))) AS ActualShiftEnd
         FROM #Calendar C
              LEFT JOIN #Emp_Shift ES ON DAY(c.AttnDt) = es.ShiftDays
              LEFT JOIN #Shift S ON s.shift_id = es.ShiftID),
     CTE1
     AS (SELECT *,
                DATEADD(minute, -@MarginStart, ActualShiftStart) MarginShiftStart,
                DATEADD(minute, @MarginEnd, ActualShiftEnd) MarginShiftEnd
         FROM CTE)
     SELECT a.*,
            DATEDIFF(minute, InTime, OutTime) TimeWork,
            CASE
                WHEN InTime > ActualShiftStart
                THEN DATEDIFF(minute, ActualShiftStart, InTime)
            END LateBy,
            CASE
                WHEN ActualShiftEnd > OutTime
                THEN DATEDIFF(minute, OutTime, ActualShiftEnd)
            END EarlyGoing,
            OutTime
     FROM CTE1 A
          OUTER APPLY
     (
         SELECT MIN(DT) InTime,
                MAX(dt) OutTime
         FROM #temp t
         WHERE t.DT >= a.MarginShiftStart
               AND t.dt <= MarginShiftEnd
     ) t;
  1. 根据实际需求和生成的行数,可以优化此脚本。
  2. 日历表可以是永久的,如果要生成 1 或 2 个月的出勤率,则可以从中生成 #Calendar。在我的脚本中,CalendarDate 是永久表。您可以以任何您喜欢的方式生成 #Calendar 的数据。
  3. [#Shift] : 在这个表中我没有使用day_end_time,因为它很难计算。我使用 day_start_time 和 DutyMinute 来计算结束时间。
  4. [#Emp_Shift] :您每天存储 Shift ID 的方式是错误的。很难以这种方式进行操作。您最好根据 #Emp_Shift 更改或旋转您的表。
  5. 您可以分析Select * from CTE 以实际了解数据是如何生成的。
  6. 您可以再次使用Select * from CTE1 来检查MarginShiftStartMarginShiftEnd。如果不清楚,可以举个例子问我。
  7. 最好在查询完成后根据需求和实际数据进行 Index Tune。就像您将生成 Attendance of 500 emp for one month 左右一样。如果脚本没问题,那么您可以使用实际场景。

创建日历表,

CREATE TABLE [dbo].[CalendarDate](
[Dates] [datetime2](0) NOT NULL
PRIMARY KEY CLUSTERED 
(
 [Dates] ASC
)
) ON [PRIMARY]
GO

 

insert into [CalendarDate] with(tablock)
select top (1000000) 
dateadd(day,ROW_NUMBER()over(order by (select null)),'1949-12-31 00:00:00') 
from sys.objects a, sys.objects b, sys.objects c

【讨论】:

    【解决方案2】:

    如果有帮助可以查看下面的查询

        Select emp_reader_id, DT,
        case when convert(char(8), DT, 108) 
         BETWEEN
        day_starttime and day_endtime  
        When
        shifttime=0 then
        convert(char(8), DT, 111)  ELSE 
         convert(char(8), DT+1, 111)  
          End as "belongs to" 
         From
        Trnevents
    

    上面的查询是我没有加入的示例,但它会检查开始时间和结束时间之间的时间,如果满足则相同的日期是 op else date +1

    【讨论】:

      【解决方案3】:

      我正在处理您的查询。请检查以下查询。可能有用。

      --Step 1.----------------------------------
      create table trnevents 
      (
      emp_reader_id int,
      DT datetime
      )
      
      insert into trnevents values
      (1066,'2019-08-10 19:02:41.000'),
      (1066,'2019-08-10 07:10:41.000'),
      (1066,'2019-08-10 07:02:41.000'),
      (1066,'2019-08-09 18:59:02.000'),
      (1066,'2019-08-09 07:00:12.000'),
      (1066,'2019-08-08 18:57:49.000'),
      (1066,'2019-08-08 07:02:14.000'),
      (1066,'2019-08-07 18:55:37.000'),
      (1066,'2019-08-07 07:01:27.000'),
      (1066,'2019-08-06 18:57:29.000'),
      (1066,'2019-08-06 07:01:10.000')
      
      --Step 2.-------------------------------------
      create table emp_shift
      (
      emp_reader_id int,
      shift_date date,
      shift_id int
      )
      
      insert into emp_shift values
      (1066,'2019-08-05',14),
      (1066,'2019-08-06',14),
      (1066,'2019-08-07',14),
      (1066,'2019-08-08',14),
      (1066,'2019-08-09',14),
      (1066,'2019-08-10',13),
      (1066,'2019-08-11',13)
      
      --Step 3.-----------------------------------
      create table shift_master
      (
      shift_id int,
      shift_start_time time,
      shift_end_time time,
      night_shift bit
      )
      
      insert into shift_master values
      (13,'07:00','19:00',0),
      (14,'19:00','07:00',1)
      
      --Step 3. Create temporary table for workings--
      
      select *,cast('N' as varchar(2)) as updatetag,CAST('' as datetime) as belongs_to into #tmptrnevents from trnevents
      
      --Step 5. Getting date belongs to.
      
      declare @emp_reader_id int, @shift_date date,@night_shift bit
      declare @start datetime,@end datetime
      
      declare  cur_shift cursor
      FOR SELECT a.emp_reader_id,a.shift_date,b.night_shift 
      FROM emp_shift a
      inner join  shift_master b
      on a.shift_id=b.shift_id order by a.shift_date
          OPEN cur_shift  
          FETCH NEXT FROM cur_shift into @emp_reader_id , @shift_date ,@night_shift  
          WHILE @@FETCH_STATUS = 0  
              BEGIN
      
               if (@night_shift=1) --if it is night shift
               begin
               select @start=isnull(MAX(dt),@shift_date)  from #tmptrnevents where emp_reader_id=@emp_reader_id and cast(DT as date)=@shift_date  and updatetag='N' 
               select @end=isnull(MIN(dt),@shift_date)  from #tmptrnevents where emp_reader_id=@emp_reader_id and cast(DT as date)=dateadd(d,1,@shift_date)  and updatetag='N' --to get next day shift
               end
      
               if (@night_shift=0) --if it is not night shift
               begin
               select @start=isnull(MIN(dt),@shift_date), @end= isnull(MAX(dt),@shift_date) from #tmptrnevents where emp_reader_id=@emp_reader_id and cast(DT as date)=@shift_date  and updatetag='N' 
               end
      
               update #tmptrnevents set belongs_to= @shift_date,updatetag='Y' where emp_reader_id=@emp_reader_id and DT between @start and @end
      
               FETCH NEXT FROM cur_shift into @emp_reader_id , @shift_date ,@night_shift  
      
              END  
      
          CLOSE cur_shift  
          DEALLOCATE cur_shift  
      
          select emp_reader_id,DT,belongs_to from #tmptrnevents
      
          drop table #tmptrnevents
      

      【讨论】:

      • 如果需要光标,我会感到惊讶。游标通常是一个坏主意,因为 SQL 是一种基于 SET 的语言,并且通过使用游标,您会强制 SQL Server 使用 Row By Agonizing Row (RBAR) 方法。很少有基于游标的方法不能用基于 SET 的方法替换并产生显着的性能提升。见blogs.msdn.microsoft.com/sqlprogrammability/2008/03/18/…
      • 我根据输出工作。基于集合的过程可能会有所帮助。将检查您的链接。
      猜你喜欢
      • 1970-01-01
      • 2011-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-01
      • 2014-01-19
      • 1970-01-01
      相关资源
      最近更新 更多