任何人都可以帮忙解决这个问题吗?我们的考勤系统生成以下数据:
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
读者价值,
我正在寻找在MS SQL 2005上运行的SQL查询,每月总结每位员工的出勤时间,例如
User Department Month Time
A1 IT 3/2014 10.34
B1 IT 3/2014 01:00
答案 0 :(得分:3)
由于需要在数据中找到转换和范围,因此使用SQL解决这个问题相当困难,这并非易事。我已经将问题分解为一系列由连续的cte组成的步骤,这些步骤相互构建并导致最终的工作解决方案:
首先,我向数据添加行索引,以提供用于标识唯一行的简单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]
)
接下来,我将这些放在一起以生成所有退出/输入范围的列表,这些范围是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 |
可以根据需要对格式进行一些调整,但这应该很容易在此框架之上构建。
这是一个有效的演示:http://www.sqlfiddle.com/#!3/f3f37/7