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数据库,可是却不能连接本地的数据库,后来发现缺少一些服务,于是决定重新安装,但是卸载却很麻烦,如果卸载不干净 ...
随机推荐
- Django中的QuerySet查询优化之select_related
在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能.本文通过一个简单的例子详解这两个函数的作用.虽然Q ...
- Linux进程间通信
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们在Linux信号基础中已经说明,信号可以看作一种粗糙的进程间通信(IPC, i ...
- git中常用命令小结
提交过程 查看文件改动以及新增的文件 git status 添加新增文件 git add your_file_path // 添加全部文件 git add * // 添加某类型文件 提交文件 git ...
- java的4种代码块
一.普通代码块 直接在一个方法中出现的{}就称为普通代码块,例子程序如下: public class CodeDemo01{ public static void main(String[] args ...
- mysql存储过程语法及实例
存储过程如同一门程序设计语言,同样包含了数据类型.流程控制.输入和输出和它自己的函数库. --------------------基本语法-------------------- 一.创建存储过程cr ...
- 【java基础】面向对象的三大基本特征之-------继承
面向对象的三大特征:封装,继承,多态 java通过extends关键字来实现继承,而且是单继承,一个子类只可以有一个直接父类,但是父类还可以有父类... java.long.Object是所有类的父类 ...
- gvim 快速编辑例子
1 准备工作 首先, 我们打开 VIM, 输入一段文本, 用于今天的演示: this is a test 2 查找替换 按几下 ESC 进入 Normal 模式, 输入以下命令: :%s/ /\r/ ...
- jQurey Plugin
; (function ($, window, document, undefined) { "use strict"; var defaults = { name: " ...
- css样式之 direction
今天看到 direction:rtl.一时有点发傻. 其实就是文本读取顺序.默认是ltr从左向右读.rtl是从右向左读取.
- windows server 2003(64位)上利用iis6部署32位应用
如果直接部署,会出现如下问题: 试图加载格式不正确的程序. (Exception from HRESULT: 0x8007000B) 解决办法 1.命令行键入: cscript.exe %SYSTEM ...