SQL查询一个月的考勤

任何人都可以帮助这个吗? 我们的考勤系统产生以下数据:

User Department Date Time Reader A1 IT 1/3/2014 11:12:00 1 B1 IT 1/3/2014 12:28:06 1 B1 IT 1/3/2014 12:28:07 1 A1 IT 1/3/2014 13:12:00 2 B1 IT 1/3/2014 13:28:06 2 A1 IT 2/3/2014 07:42:15 1 A1 IT 2/3/2014 16:16:15 2 

读者价值在哪里,

  • 1 =input
  • 2 =退出

例如,我正在寻找在MS SQL 2005上运行的SQL查询,以每月为每个员工总结出勤时间

 User Department Month Time A1 IT 3/2014 10.34 B1 IT 3/2014 01:00 

由于需要在数据中查找转换和范围,这是一个相当难以用SQL解决的问题,这并不是微不足道的。 我已经把问题分解成了一系列由连续的cte组成的步骤,这些步骤互相构build并导致最终的工作解决scheme:

首先,我给数据添加一个行索引来提供一个简单的PK来识别一个唯一的行:

 with NumberedAtt as ( select row_number() over (partition by [user] order by date, time, reader) as ix, att.[user], att.[department], att.[date] + att.[time] as dt, att.[reader] from att ) 

然后我抓取每个用户的第一个和最后一个索引值,用于每个入口/出口范围的最外边界:

 , MinMax as ( select [user], min(ix) ixMin, max(ix) ixMax from NumberedAtt N group by [user] ) 

接下来,我将它们放在一起生成所有退出/input范围的列表,这些列表是Reader的值从2变为1 。 这些是特定的点,确切地确定前一个时间范围结束,下一个时间范围开始(干净地处理连续的重复进入/退出读取)。 通过将这与每个用户的第一个入口和最后一个出口时间相结合,将生成所有入口/出口转换的列表:

 , Transitions as ( select N.[User], 0 as exitIx, M.ixMin as entryIx from NumberedAtt N join MinMax M on N.[User] = M.[User] where N.ix = M.ixMin union select N.[User], M.ixMax as exitIx, 0 as entryIx from NumberedAtt N join MinMax M on N.[User] = M.[User] where N.ix = M.ixMax union select A1.[User], A1.ix as exitIx, A2.ix as entryIx from NumberedAtt A1 join NumberedAtt A2 on A1.ix + 1 = A2.ix and A1.[user] = A2.[user] where A1.[reader] = 2 and A2.[reader] = 1 ) 

这里是这个输出:

 | USER | EXITIX | ENTRYIX | |------|--------|---------| | A1 | 0 | 1 | | A1 | 2 | 3 | | A1 | 4 | 0 | | B1 | 0 | 1 | | B1 | 3 | 0 | 

请注意,我们整齐地捕获了一系列时间开始和结束的行索引。 但是,它们被偏移了一个 – 即一行中的入口时间对应于下一行中的退出时间。 因此,我们需要再进行一次转换,通过向此表添加行索引并将每行连接到下一行,从而将范围恢复到一起:

 , NumberedTransitions as ( select row_number() over (partition by [User] order by exitIx) tix, T.* from Transitions T ), EntryExit as ( select aEntry.ix as ixEntry, aExit.ix as ixExit, aEntry.[user], aEntry.[department], aEntry.[dt] as entryDT, aExit.[dt] as exitDT from NumberedTransitions tEntry join NumberedAtt aEntry on tEntry.entryIx = aEntry.ix and tEntry.[user] = aEntry.[user] join NumberedTransitions tExit on tEntry.tix + 1 = tExit.tix and tEntry.[user] = tExit.[user] join NumberedAtt aExit on tExit.exitIx = aExit.ix and tExit.[user] = aExit.[user] ) 

在将连续的范围连接在一起之后,我还将原始的详细数据返回,因为我一直只使用行索引值。 在这个子查询的末尾,我们已经确定了每个用户的所有进入/退出范围,并“吞噬”了任何多个读取:

 | IXENTRY | IXEXIT | USER | DEPARTMENT | ENTRYDT | EXITDT | |---------|--------|------|------------|------------------------------|------------------------------| | 1 | 2 | A1 | IT | March, 01 2014 11:12:00+0000 | March, 01 2014 13:12:00+0000 | | 3 | 4 | A1 | IT | March, 02 2014 07:42:15+0000 | March, 02 2014 16:16:15+0000 | | 1 | 3 | B1 | IT | March, 01 2014 12:28:06+0000 | March, 01 2014 13:28:06+0000 | 

现在唯一要做的就是将数据分组在一起,以报告每个用户每月的总小时数。 计算总小时数有点棘手,但可以通过取范围之间的分钟数然后将结果转换回时间值来完成:

 , Hours as ( select [User], [Department], Year(EntryDT) Year, Month(EntryDT) Month, RIGHT('0' + CAST(SUM(DATEDIFF(Minute, EntryDT, ExitDT)) / 60 as varchar(10)), 2) + ':' + RIGHT('0' + CAST(SUM(DATEDIFF(Minute, EntryDT, ExitDT)) % 60 as varchar(2)), 2) as TotalHours from EntryExit EE group by [User], [Department], Year(EntryDT), Month(EntryDT) ) 

这给出了非常接近期望结果的最终结果:

 | USER | DEPARTMENT | YEAR | MONTH | TOTALHOURS | |------|------------|------|-------|------------| | A1 | IT | 2014 | 3 | 10:34:00 | | B1 | IT | 2014 | 3 | 01:00:00 | 

可以根据需要对格式进行一些调整,但应该很容易在此框架之上进行构build。

这是一个工作演示: http ://www.sqlfiddle.com/#!3/ f3f37/7

Interesting Posts