最近,遇到并解决一个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. 兼容IE7音乐播放器之jplayer的使用

    首先列出为何要写这篇随笔的原因: 1:兼容IE7 2:音乐播放器 3:任意控制播放器 1: 最近做的网站需要兼容IE7,在此之前已经写好了关于音乐播放的插件,火狐,IE8以上,以及谷歌浏览器等都可以随 ...

  2. ubuntu 配置 jdk的一些常见问题

    首先下好压缩包.我用的是tar.gz 然后在/usr下解压,用mv或rename修改下文件夹名字,省得名字太长,然后再配置环境变量. so easy. 然而,  接下来你肯定会用 source /et ...

  3. JVM 内存管理机制

    1. 内存分配图:  两栈一区一堆一计数 方法区里面 包含了运行时常量 2. 对象创建过程: new A() 首先加载A的字节码. 分配内存,内存分配方式分两种,如果采用带压缩的垃圾回收策略,则采用“ ...

  4. C++学习注意点

    1.cin,cout关同步再用,不然效率很糟cin,cout关同步再用,不然效率很糟cin,cout关同步再用,不然效率很糟.重要的事情说三遍.关同步代码:std::ios::sync_with_st ...

  5. 修改CMD字符编码

    1.参考网址: 1.1.http://blog.useasp.net/archive/2012/04/24/how_to_use_UTF8_encoding_in_Windows_CMD.aspx 1 ...

  6. Java链接MySQL练习题:格式化日期、性别;避免代码注入

    一.查询人员名单,按序号 姓名 性格(男或女) 民族(某族) 生日(年月日)输出 import java.sql.*; import java.text.SimpleDateFormat; publi ...

  7. iOS 开发之崩溃日志分析

    1. (js 与webview 交互崩溃)-[CFRunLoopTimer release]: message sent to deallocated instance 0x62398f80 I've ...

  8. Spring MVC Rest服务 返回json报406错误的解决办法

    @ResponseBody & @RequestBody @RequestBody 将 HTTP 请求正文插入方法中,使用适合的HttpMessageConverter将请求体写入某个对象. ...

  9. springmvc接收JSON类型的数据

    1.在使用AJAX传递JSON数据的时候要将contentType的类型设置为"application/json",否则的话会提示415错误 2.传递的data需要时JSON类型的 ...

  10. java获取年份的后两位

    public static String getDate(Date strDate) { String date = null; if (strDate!= null) { Calendar star ...