SQL疑難問題

jopen 9年前發布 | 8K 次閱讀 SQL

最近,遇到并解決一個SQL上的疑難問題。考勤系統,記錄著員工進出公司的刷卡記錄。而員工刷卡并不規范,存在刷多次的情況。例如:出去時連續刷多次,進來時也連續刷多次。篩選有效刷卡記錄數據的規則: 對于出去時連續刷多次(包含一次)的情況,取第一次刷卡記錄;對于進來時連續刷多次(包含一次)的情況,取最后一次的刷卡記錄。 考勤系統的數據量很大,假設公司有2萬名員工,一員工一天100條刷卡記錄。

用什么方法可以高效地查出某一時間范圍內員工的有效刷卡記錄?

測試表及測試數據如下:

create table Attendance
(
UserId  int,        --員工ID
ClockInTime datetime,   --員工刷卡時間
Flag char(1)        --進出標志 '1'代表出,'0'代表進   
) 
insert Attendance
values(100001,'2015-06-01 08:03:00','1'),
  (100001,'2015-06-01 08:03:10','1'),
  (100001,'2015-06-01 08:03:50','1'),
  (100001,'2015-06-01 08:04:00','1'),
  (100001,'2015-06-01 08:10:00','0'),
  (100001,'2015-06-01 08:10:10','0'),
  (100001,'2015-06-01 08:15:00','1'),
  (100001,'2015-06-01 08:30:00','1'),
  (100001,'2015-06-01 08:40:10','0'),
  (100001,'2015-06-01 09:00:00','1'),
  (100001,'2015-06-01 09:15:10','0'),
  (100001,'2015-06-01 09:30:00','1'),
  (100002,'2015-06-01 08:03:00','0'),
  (100002,'2015-06-01 08:03:10','0'),
  (100002,'2015-06-01 08:03:50','1'),
  (100002,'2015-06-01 08:04:00','1'),
  (100002,'2015-06-01 08:10:00','1'),
  (100002,'2015-06-01 08:10:10','0'),
  (100002,'2015-06-01 08:15:00','1'),
  (100002,'2015-06-01 08:30:00','1'),
  (100002,'2015-06-01 08:40:10','1'),
  (100002,'2015-06-01 09:00:00','0'),
  (100002,'2015-06-01 09:15:10','0'),
  (100002,'2015-06-01 09:30:00','1')

而需篩選的有效數據為紅色標記部分:

SQL疑難問題

而作為測試數據,也就只提供兩名員工,每人一天12條的刷卡記錄,需要完成的是將紅色標記的數據篩選出來。

不難看出問題的難點在于判斷哪些數據是連續(進或出)的,無論出去還是進來。把這一點解決了,所有的問題也就迎刃而解。

1)首先,想到了遞歸查詢。但是很快否定了想法,這個方法判斷不出來數據是否連續。

2)其次,又考慮游標。或許游標能判斷是否連續的問題,但是處理大數據量時,性能肯定極其低。

最后,閃現一個思路,沒想到順著這個思路把問題解決了。

1,先按UserID、日期分組,組內按ClockInTime升序排列。

select *,
ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime,23) order by ClockInTime) as RN into #tmp
 from Attendance

select * from #tmp order by UserId,ClockInTime</pre>

結果如圖:

SQL疑難問題

2,再按UserID、日期、Flag分組,組內按ClockInTime升序排列。

select *,
ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime),Flag order by ClockInTime) as RN1 into #tmp1
from Attendance

select * from #tmp1 order by UserId,ClockInTime</pre>

結果如圖:

SQL疑難問題

3,用#tmp1中的RN1與#tmp中的RN做差。

select a.*,b.RN1,b.RN1-a.RN as RN2 into #tmp2 from #tmp as a,#tmp1 as b where a.UserId=b.UserId and a.ClockInTime=b.ClockInTime and a.Flag=b.Flag

select * from #tmp2 order by UserId,ClockInTime</pre>

結果如圖:

SQL疑難問題

3,根據UserID、日期、Flag、RN2可以判斷出哪些數據是連續的,然后,用Row_Number()排序一下,就可以篩選所需要的數據。

select *,
 case when Flag='1' then ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN2 order by ClockInTime) 
 else ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN2 order by ClockInTime desc) end as RId
 into #tmp3
 from #tmp2

select * from #tmp3 order by UserId,ClockInTime</pre>

結果如圖:

SQL疑難問題

4,RID=‘1’的數據是正確的結果,即有效的刷卡記錄數據。

select UserId,ClockInTime,Flag 
from #tmp3 
where Rid='1' 
order by UserId,ClockInTime

結果如圖:

SQL疑難問題

這樣問題就解決了。進一步優化sql,其實1,2,3等3個步驟只要一步就解決了

select *,
ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime,23) order by ClockInTime)-ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime),Flag order by ClockInTime) as RN
from Attendance order by UserId,ClockInTime

有了上面查詢的結果,后面的查詢也簡單多了。如果用一句SQL來解決的話,那就是:

select UserId,ClockInTime,Flag from (
select *,
case when Flag='1' then ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN order by ClockInTime) 
else ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN order by ClockInTime desc) end as RId
from (
select *,
ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime,23) order by ClockInTime)-ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime),Flag order by ClockInTime) as RN
from Attendance
) as a
) as b 
where RId='1' order by UserId,ClockInTime
原文 http://www.cnblogs.com/FlyGoldfish/p/4593390.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!