最近,遇到并解决一个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. Python if 和 for 的多种写法

    a, b, c = 1, 2, 3 [对比Cpp里:c = a >b? a:b]这个写法,Python只能常规的空行,缩进吗? 人生苦短,我用python,下面介绍几种if的方便的方法. 1.常 ...

  2. java基本数据类型的字面量

    java的基本数据类型一共有8种.其中:(每种类型后面列出了java中的该类型的字面量) 四种整型: int  4字节: 12  +12  -12  077 0xFF  0b101(JDK7中支持的二 ...

  3. codeforces 744C Hongcow Buys a Deck of Cards

    C. Hongcow Buys a Deck of Cards time limit per test 2 seconds memory limit per test 256 megabytes in ...

  4. 在Ubuntu下使用 csapp.h 和 csapp.c

    它山之石可以攻玉. 对于<深入理解计算机系统>这本神人写就的神书, 我等凡人就不评论什么啦. 这本书的 第二,三 部分, 真的真的对我理解操作系统有很大的帮助. (当然, 如果你不看第一部 ...

  5. 1012. The Best Rank (25)

    To evaluate the performance of our first year CS majored students, we consider their grades of three ...

  6. 个人博客作业Week3

    一.调研 下载并使用,按照描述的bug定义,找出几个功能性的比较严重的bug.至少两个.用专业的语言描述(每个bug 不少于 40字),如有必要,可以配图. 电脑用户未登录就能使用单词本功能,万一是用 ...

  7. JSPatch学习笔记

    本文参考JSPatch wiki :https://github.com/bang590/JSPatch/wiki 1.概念 JSPatch是一个轻量的JS引擎,能够使用JavaScript语言来调用 ...

  8. 【第1期】腾讯云的1001种玩法征集,Ipad mini和Kindle 等你拿!(文章评审中)

    版权声明:本文由阁主的小跟班原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/695994001482226944 来源:腾云 ...

  9. 【javascript】作用域和闭包浅析

    作用域 分全局作用域和局部作用域 全局作用域:函数外部定义的变量,可以被整个program的各成员参照利用. 局部作用域:函数内部定义的变量,仅供该函数的各成员参照利用. var val=1; //全 ...

  10. spring 驱动模式

    驱动模式 public class ProAction extends ActionSupport implements ModelDriven<Product>{ //驱动模式 priv ...