最近,遇到并解决一个SQL上的疑难问题。考勤系统,记录着员工进出公司的刷卡记录。而员工刷卡并不规范,存在刷多次的情况。例如:出去时连续刷多次,进来时也连续刷多次。筛选有效刷卡记录数据的规则:对于出去时连续刷多次(包含一次)的情况,取第一次刷卡记录;对于进来时连续刷多次(包含一次)的情况,取最后一次的刷卡记录。考勤系统的数据量很大,假设公司有2万名员工,一员工一天100条刷卡记录。

用什么方法可以高效地查出某一时间范围内员工的有效刷卡记录?

测试表及测试数据如下:

  1. create table Attendance
  2. (
  3. UserId int, --员工ID
  4. ClockInTime datetime, --员工刷卡时间
  5. Flag char(1) --进出标志 '1'代表出,'0'代表进
  6. )
  7.  
  8. insert Attendance
  9. values(100001,'2015-06-01 08:03:00',''),
  10. (100001,'2015-06-01 08:03:10',''),
  11. (100001,'2015-06-01 08:03:50',''),
  12. (100001,'2015-06-01 08:04:00',''),
  13. (100001,'2015-06-01 08:10:00',''),
  14. (100001,'2015-06-01 08:10:10',''),
  15. (100001,'2015-06-01 08:15:00',''),
  16. (100001,'2015-06-01 08:30:00',''),
  17. (100001,'2015-06-01 08:40:10',''),
  18. (100001,'2015-06-01 09:00:00',''),
  19. (100001,'2015-06-01 09:15:10',''),
  20. (100001,'2015-06-01 09:30:00',''),
  21. (100002,'2015-06-01 08:03:00',''),
  22. (100002,'2015-06-01 08:03:10',''),
  23. (100002,'2015-06-01 08:03:50',''),
  24. (100002,'2015-06-01 08:04:00',''),
  25. (100002,'2015-06-01 08:10:00',''),
  26. (100002,'2015-06-01 08:10:10',''),
  27. (100002,'2015-06-01 08:15:00',''),
  28. (100002,'2015-06-01 08:30:00',''),
  29. (100002,'2015-06-01 08:40:10',''),
  30. (100002,'2015-06-01 09:00:00',''),
  31. (100002,'2015-06-01 09:15:10',''),
  32. (100002,'2015-06-01 09:30:00','')

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

而作为测试数据,也就只提供两名员工,每人一天12条的刷卡记录,需要完成的是将红色标记的数据筛选出来。

不难看出问题的难点在于判断哪些数据是连续(进或出)的,无论出去还是进来。把这一点解决了,所有的问题也就迎刃而解。

1)首先,想到了递归查询。但是很快否定了想法,这个方法判断不出来数据是否连续。

2)其次,又考虑游标。或许游标能判断是否连续的问题,但是处理大数据量时,性能肯定极其低。

最后,闪现一个思路,没想到顺着这个思路把问题解决了。

1,先按UserID、日期分组,组内按ClockInTime升序排列。

  1. select *,
  2. ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime,23) order by ClockInTime) as RN into #tmp
  3. from Attendance
  4.  
  5. select * from #tmp order by UserId,ClockInTime

结果如图:

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

  1. select *,
  2. ROW_NUMBER() over(partition by UserId,convert(varchar(10),ClockInTime),Flag order by ClockInTime) as RN1 into #tmp1
  3. from Attendance
  4.  
  5. select * from #tmp1 order by UserId,ClockInTime

结果如图:

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

  1. select a.*,b.RN1,b.RN1-a.RN as RN2 into #tmp2
  2. from #tmp as a,#tmp1 as b
  3. where a.UserId=b.UserId and a.ClockInTime=b.ClockInTime and a.Flag=b.Flag
  4.  
  5. select * from #tmp2 order by UserId,ClockInTime

结果如图:

3,根据UserID、日期、Flag、RN2可以判断出哪些数据是连续的,然后,用Row_Number()排序一下,就可以筛选所需要的数据。

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

结果如图:

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

  1. select UserId,ClockInTime,Flag
  2. from #tmp3
  3. where Rid='1'
  4. order by UserId,ClockInTime

结果如图:

这样问题就解决了。进一步优化sql,其实1,2,3等3个步骤只要一步就解决了

  1. select *,
  2. 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
  3. from Attendance order by UserId,ClockInTime

有了上面查询的结果,后面的查询也简单多了。如果用一句SQL来解决的话,那就是:

  1. select UserId,ClockInTime,Flag from (
  2. select *,
  3. case when Flag='1' then ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN order by ClockInTime)
  4. else ROW_NUMBER() over(Partition by UserID,convert(varchar(10),ClockInTime,23),Flag,RN order by ClockInTime desc) end as RId
  5. from (
  6. select *,
  7. 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
  8. from Attendance
  9. ) as a
  10. ) as b
  11. 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. test lab ~ triangle test by using junit and coverage

    first set up a new folder as your test class place, and then let your package in test class folder b ...

  2. php : MVC 演示(使用单例工厂)

    此例子是MVC的简单应用, 要达到的效果如下: 用户列表: 姓名 年龄 学历 兴趣 出生地 账号创建时间 操作 keen 20 高中 篮球,足球 广东 2016-11-08 10:00:31 删除 a ...

  3. Meta标签用法大全

    meta是html文档在head标签里定义的一个对文档进行描述的功能性标签 meta标签有下面的作用: 1.搜索引擎优化(SEO) 2.定义页面使用语言 3.自动刷新并指向新的页面 4.实现网页转换时 ...

  4. arm-linux-androideabi-addr2line

    ./arm-linux-androideabi-addr2line -C -f -e libc.so 00040d94

  5. redsocks 将socks代理转换成全局代理

    redsocks 需要手动下载编译.前置需求为libevent组件,当然gcc什么的肯定是必须的. 获取源码 git clone https://github.com/darkk/redsocks 安 ...

  6. jQuery Mobile学习日记之HelloWorld

    这里是本人学习jQuery Mobile的过程,主要用于记录,过程中有不对的地方或不严谨的地方,欢予以指出纠正,非常感谢! 1.首先是下载安装以下文件: [Opera Mobile Emulator] ...

  7. NGUI Atlas Maker sprites with black line issue

    NGUI图集中的图,在游戏中显示出来带有黑边的问题. 实际上是因为图片在导入到图集中,图片四周的完全透明的边缘部分会被裁掉,而在图集中的实际大小比图片原始大小小以及图集中图片之间的间距设置得太小导致. ...

  8. yum源安装Mysql

    Mysql版本:5.7 进入mysql官网,复制下载链接 步骤: (1) wget  http://dev.mysql.com/get/mysql57-community-release-el6-9. ...

  9. svn快速教程

    本文拷贝自网址:http://www.subversion.org.cn/?action-viewnews-itemid-1 如何快速建立Subversion服务器,并且在项目中使用起来,这是大家最关 ...

  10. AFNetworking到底做了什么

    写在开头: 作为一个iOS开发,也许你不知道NSUrlRequest.不知道NSUrlConnection.也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定 ...