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',''),
(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疑难问题的更多相关文章
- Oracle SQL 疑难解析读书笔记(一 基础)
1.在语句中找到和消除空值 select first_name,last_name from hr.employees where commission_pct is null is null 和 i ...
- Oracle SQL 疑难解析读书笔记(二、汇总和聚合数据)
2.1 对某字段的值进行汇总 仅仅在两种特殊情况下,Oracle在聚合函数中考虑了NULL值.第一种是在GROUPING功能里,用来检验包含了NULL值的分析函数的结果,是直接由所在的表得来,还是由分 ...
- sql语句聚合等疑难问题收集
------------------------------------------------------------------------------------ 除法运算 select 500 ...
- SQL SERVER 2012疑难问题解决方法
问题一: 问题描述 SQL SERVER 2012 尝试读取或写入受保护的内存.这通常指示其他内存已损坏. (System.Data) 解决办法 管理员身份运行 cmd -> 输入 netsh ...
- 微软MVP攻略 (如何成为MVP?一个SQL Server MVP的经验之谈)
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 初衷 什么是微软MVP? 成为微软MVP的条件? 如何成为微软MVP? (一) 申请时间划分 (二) 前期准备 (三) ...
- Spark 官方文档(5)——Spark SQL,DataFrames和Datasets 指南
Spark版本:1.6.2 概览 Spark SQL用于处理结构化数据,与Spark RDD API不同,它提供更多关于数据结构信息和计算任务运行信息的接口,Spark SQL内部使用这些额外的信息完 ...
- SQL Server 2008 R2——CROSS APPLY 根据数据出现的次数和时间来给新字段赋值
=================================版权声明================================= 版权声明:原创文章 禁止转载 请通过右侧公告中的“联系邮 ...
- SQL Server代理(4/12):配置数据库邮件
SQL Server代理是所有实时数据库的核心.代理有很多不明显的用法,因此系统的知识,对于开发人员还是DBA都是有用的.这系列文章会通俗介绍它的很多用法. 在以前的文章里我们看到,SQL Serve ...
- 如何彻底的卸载sql server数据库
如何彻底的卸载sql server数据库 好不容易装上了sql server 2012数据库,可是却不能连接本地的数据库,后来发现缺少一些服务,于是决定重新安装,但是卸载却很麻烦,如果卸载不干净 ...
随机推荐
- 欧拉回路(hdu3018)
刚学图论不久,看着别人的博客慢慢学了一点基础的,感觉还是有点力不从心,感觉图论的题好多长得都很像,什么太监算法(Tarjan),Kosaraju,当然最基础的还是并查集...好了继续介绍这道题.... ...
- maven命令参考简要
命令参考简要说明 mvn archetype:generate — 创建生成Tiny骨架工程 参数名 说明 groupId 用户项目的包目录,用户需要根据实际情况设置.比如com.abc artifa ...
- C语言习题(结构)
实际应用中经常会用到二维平面上的点,点的操作包括设置点的位置( pointT setPoint(double x , double y ) ),显示第n个点的位置( void showPoint(po ...
- Java多线程初探
多线程 单线程的程序只有一个顺序执行流.多个顺序流之间互不干扰. 多线程的创建 定义Thread类的子类,重写该类的run()方法. 创建Thread子类的实例. 调用线程对象的start()方法来启 ...
- svn使用
1 svn地址: svn://www.svn999.com/annie123.test12345 2 svn 账户: 账户使用时期:项目checkout时期 会提示键入用户名密码 3 新增文件 (1) ...
- STM32命名原则
每种STM32的产品都由16个字母或数字构成的编号标示,用户向ST订货时必须使用这个编号指定需要的产品.这16个字符分为8个部分,下面通过一个例子说明它们的意义: STM32 F 103 C ...
- RANSAC 剔除错误匹配 估计模型
随机抽样一致,这个算法,我以前一直都没有理解透彻.只知道可以用来直线拟合,网上大多数中文博客也都是写直线拟合的,但是用来匹配二维特征的时候,总还是没弄明白. 基本概念参考 http://www.cnb ...
- 微信微信JS-SDK 6.0.2 填坑笔记
0.为什么以前不需要配置这么麻烦就可以修改分享description 等信息,但是现在不行了. 因为6.0.2版本之前没有做权限验证,所以config都是ok,但这并不意味着你config中的签名是O ...
- 兼容cookie和webStorage
html页面 <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...
- Excel中提取最大值的问题
在使用excel的时候,碰到了一个如下的问题 意思是以每个字母为条件,取这个字母下面的数字中的最大值,需要注意一个问题是每个字母下面的数字个数不一定相等,例如d下面有四个数字,可以设置如下公式解决: ...