最近,遇到并解决一个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',''),
(100001,'2015-06-01 08:03:10',''),
(100001,'2015-06-01 08:03:50',''),
(100001,'2015-06-01 08:04:00',''),
(100001,'2015-06-01 08:10:00',''),
(100001,'2015-06-01 08:10:10',''),
(100001,'2015-06-01 08:15:00',''),
(100001,'2015-06-01 08:30:00',''),
(100001,'2015-06-01 08:40:10',''),
(100001,'2015-06-01 09:00:00',''),
(100001,'2015-06-01 09:15:10',''),
(100001,'2015-06-01 09:30:00',''),
(100002,'2015-06-01 08:03:00',''),
(100002,'2015-06-01 08:03:10',''),
(100002,'2015-06-01 08:03:50',''),
(100002,'2015-06-01 08:04:00',''),
(100002,'2015-06-01 08:10:00',''),
(100002,'2015-06-01 08:10:10',''),
(100002,'2015-06-01 08:15:00',''),
(100002,'2015-06-01 08:30:00',''),
(100002,'2015-06-01 08:40:10',''),
(100002,'2015-06-01 09:00:00',''),
(100002,'2015-06-01 09:15:10',''),
(100002,'2015-06-01 09:30:00','')

而需筛选的有效数据为红色标记部分:

而作为测试数据,也就只提供两名员工,每人一天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

结果如图:

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

结果如图:

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

结果如图:

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

结果如图:

4,RID=‘1’的数据是正确的结果,即有效的刷卡记录数据。

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

结果如图:

这样问题就解决了。进一步优化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

  

SQL疑难问题的更多相关文章

  1. Oracle SQL 疑难解析读书笔记(一 基础)

    1.在语句中找到和消除空值 select first_name,last_name from hr.employees where commission_pct is null is null 和 i ...

  2. Oracle SQL 疑难解析读书笔记(二、汇总和聚合数据)

    2.1 对某字段的值进行汇总 仅仅在两种特殊情况下,Oracle在聚合函数中考虑了NULL值.第一种是在GROUPING功能里,用来检验包含了NULL值的分析函数的结果,是直接由所在的表得来,还是由分 ...

  3. sql语句聚合等疑难问题收集

    ------------------------------------------------------------------------------------ 除法运算 select 500 ...

  4. SQL SERVER 2012疑难问题解决方法

    问题一: 问题描述 SQL SERVER 2012 尝试读取或写入受保护的内存.这通常指示其他内存已损坏. (System.Data) 解决办法 管理员身份运行 cmd ->  输入 netsh ...

  5. 微软MVP攻略 (如何成为MVP?一个SQL Server MVP的经验之谈)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 初衷 什么是微软MVP? 成为微软MVP的条件? 如何成为微软MVP? (一) 申请时间划分 (二) 前期准备 (三) ...

  6. Spark 官方文档(5)——Spark SQL,DataFrames和Datasets 指南

    Spark版本:1.6.2 概览 Spark SQL用于处理结构化数据,与Spark RDD API不同,它提供更多关于数据结构信息和计算任务运行信息的接口,Spark SQL内部使用这些额外的信息完 ...

  7. SQL Server 2008 R2——CROSS APPLY 根据数据出现的次数和时间来给新字段赋值

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

  8. SQL Server代理(4/12):配置数据库邮件

    SQL Server代理是所有实时数据库的核心.代理有很多不明显的用法,因此系统的知识,对于开发人员还是DBA都是有用的.这系列文章会通俗介绍它的很多用法. 在以前的文章里我们看到,SQL Serve ...

  9. 如何彻底的卸载sql server数据库

    如何彻底的卸载sql server数据库    好不容易装上了sql server 2012数据库,可是却不能连接本地的数据库,后来发现缺少一些服务,于是决定重新安装,但是卸载却很麻烦,如果卸载不干净 ...

随机推荐

  1. HTML5离线缓存问题

    HTML5离线缓存问题 1.应用程序缓存 什么是应用程序缓存(Application Cache)? HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. ...

  2. jquery.validate使用 - 自定义错误信息

    自定义错误消息的显示方式 默认情况下,验证提示信息用label元素来显示, 并且会添加css class, 通过css可以很方便设置出错控件以及错误信息的显示方式. /* 输入控件验证出错*/form ...

  3. 解决iscroll5在手机页面上onclick事件失效

    Iscroll.js使用之后页面上面A标签的onclick事件无效了   解决办法 实例化IScroll的时候把preventDefault设为false,默认为true var myScroll; ...

  4. Python递归报错:RuntimeError: maximum recursion depth exceeded in comparison

    Python中默认的最大递归深度是989,当尝试递归第990时便出现递归深度超限的错误: RuntimeError: maximum recursion depth exceeded in compa ...

  5. Deep Learning 23:dropout理解_之读论文“Improving neural networks by preventing co-adaptation of feature detectors”

    理论知识:Deep learning:四十一(Dropout简单理解).深度学习(二十二)Dropout浅层理解与实现.“Improving neural networks by preventing ...

  6. 20161117__Z

    1.cclplus: error: unrecognized command line option "-std=gnu++11" http://www.gowhich.com/b ...

  7. Evolutionary Computing: Assignments

    Assignment 1: TSP Travel Salesman Problem Assignment 2: TTP Travel Thief Problem The goal is to find ...

  8. phalcon: 多模块多表查找,多表sql

    那么多模块下,如何分页的,如果直接用->from(表名),报错找不到此类,此时要引用model类的全命名空间名称如下: $builder = $this->modelsManager-&g ...

  9. Rdseed与SAC的安装

    欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...

  10. 【java项目小记--JManager】项目开始原因及github部署

    --记于2016-8-11 毕业已三载,忽然想起大学时的毕业设计项目,想到曾在毕业答辩上说,会吸取老师点评并逐渐完善该项目.而今 老师点评已忘了个干净,项目也还是毕业时的样子,正好最近有些时间,打算兑 ...